2013-03-26 23:19:03 +01:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
from xml.sax import saxutils, handler, make_parser
|
|
|
|
from optparse import OptionParser
|
|
|
|
import ConfigParser
|
|
|
|
import logging
|
|
|
|
import base64
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
|
|
|
|
__VERSION = (0, 1)
|
|
|
|
|
|
|
|
'''
|
|
|
|
This tool reads a mac_permissions.xml and replaces keywords in the signature
|
|
|
|
clause with keys provided by pem files.
|
|
|
|
'''
|
|
|
|
|
|
|
|
class GenerateKeys(object):
|
|
|
|
def __init__(self, path):
|
|
|
|
'''
|
|
|
|
Generates an object with Base16 and Base64 encoded versions of the keys
|
|
|
|
found in the supplied pem file argument. PEM files can contain multiple
|
|
|
|
certs, however this seems to be unused in Android as pkg manager grabs
|
|
|
|
the first cert in the APK. This will however support multiple certs in
|
|
|
|
the resulting generation with index[0] being the first cert in the pem
|
|
|
|
file.
|
|
|
|
'''
|
|
|
|
|
|
|
|
self._base64Key = list()
|
|
|
|
self._base16Key = list()
|
|
|
|
|
|
|
|
if not os.path.isfile(path):
|
|
|
|
sys.exit("Path " + path + " does not exist or is not a file!")
|
|
|
|
|
|
|
|
pkFile = open(path, 'rb').readlines()
|
|
|
|
base64Key = ""
|
2013-10-07 00:32:05 +02:00
|
|
|
lineNo = 1
|
|
|
|
certNo = 1
|
2013-03-26 23:19:03 +01:00
|
|
|
inCert = False
|
|
|
|
for line in pkFile:
|
2013-10-07 00:32:05 +02:00
|
|
|
line = line.strip()
|
|
|
|
# Are we starting the certificate?
|
2013-10-15 00:51:48 +02:00
|
|
|
if line == "-----BEGIN CERTIFICATE-----":
|
2013-10-07 00:32:05 +02:00
|
|
|
if inCert:
|
|
|
|
sys.exit("Encountered another BEGIN CERTIFICATE without END CERTIFICATE on " +
|
|
|
|
"line: " + str(lineNo))
|
|
|
|
|
|
|
|
inCert = True
|
|
|
|
|
|
|
|
# Are we ending the ceritifcate?
|
2013-10-15 00:51:48 +02:00
|
|
|
elif line == "-----END CERTIFICATE-----":
|
2013-10-07 00:32:05 +02:00
|
|
|
if not inCert:
|
|
|
|
sys.exit("Encountered END CERTIFICATE before BEGIN CERTIFICATE on line: "
|
|
|
|
+ str(lineNo))
|
|
|
|
|
|
|
|
# If we ended the certificate trip the flag
|
|
|
|
inCert = False
|
|
|
|
|
2020-07-27 18:30:34 +02:00
|
|
|
# Check the input
|
2013-10-07 00:32:05 +02:00
|
|
|
if len(base64Key) == 0:
|
|
|
|
sys.exit("Empty certficate , certificate "+ str(certNo) + " found in file: "
|
|
|
|
+ path)
|
|
|
|
|
|
|
|
# ... and append the certificate to the list
|
|
|
|
# Base 64 includes uppercase. DO NOT tolower()
|
|
|
|
self._base64Key.append(base64Key)
|
|
|
|
try:
|
|
|
|
# Pkgmanager and setool see hex strings with lowercase, lets be consistent
|
|
|
|
self._base16Key.append(base64.b16encode(base64.b64decode(base64Key)).lower())
|
|
|
|
except TypeError:
|
|
|
|
sys.exit("Invalid certificate, certificate "+ str(certNo) + " found in file: "
|
|
|
|
+ path)
|
|
|
|
|
|
|
|
# After adding the key, reset the accumulator as pem files may have subsequent keys
|
|
|
|
base64Key=""
|
|
|
|
|
|
|
|
# And increment your cert number
|
|
|
|
certNo = certNo + 1
|
|
|
|
|
|
|
|
# If we haven't started the certificate, then we should not encounter any data
|
|
|
|
elif not inCert:
|
2013-10-10 22:37:03 +02:00
|
|
|
if line is not "":
|
|
|
|
sys.exit("Detected erroneous line \""+ line + "\" on " + str(lineNo)
|
2013-10-07 00:32:05 +02:00
|
|
|
+ " in pem file: " + path)
|
|
|
|
|
|
|
|
# else we have started the certicate and need to append the data
|
|
|
|
elif inCert:
|
|
|
|
base64Key += line
|
|
|
|
|
|
|
|
else:
|
|
|
|
# We should never hit this assert, if we do then an unaccounted for state
|
|
|
|
# was entered that was NOT addressed by the if/elif statements above
|
|
|
|
assert(False == True)
|
|
|
|
|
|
|
|
# The last thing to do before looping up is to increment line number
|
|
|
|
lineNo = lineNo + 1
|
2013-03-26 23:19:03 +01:00
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return len(self._base16Key)
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return str(self.getBase16Keys())
|
|
|
|
|
|
|
|
def getBase16Keys(self):
|
|
|
|
return self._base16Key
|
|
|
|
|
|
|
|
def getBase64Keys(self):
|
|
|
|
return self._base64Key
|
|
|
|
|
|
|
|
class ParseConfig(ConfigParser.ConfigParser):
|
|
|
|
|
|
|
|
# This must be lowercase
|
|
|
|
OPTION_WILDCARD_TAG = "all"
|
|
|
|
|
2013-03-28 03:33:02 +01:00
|
|
|
def generateKeyMap(self, target_build_variant, key_directory):
|
2013-03-26 23:19:03 +01:00
|
|
|
|
|
|
|
keyMap = dict()
|
|
|
|
|
|
|
|
for tag in self.sections():
|
|
|
|
|
|
|
|
options = self.options(tag)
|
|
|
|
|
|
|
|
for option in options:
|
|
|
|
|
|
|
|
# Only generate the key map for debug or release,
|
|
|
|
# not both!
|
|
|
|
if option != target_build_variant and \
|
|
|
|
option != ParseConfig.OPTION_WILDCARD_TAG:
|
|
|
|
logging.info("Skipping " + tag + " : " + option +
|
|
|
|
" because target build variant is set to " +
|
|
|
|
str(target_build_variant))
|
|
|
|
continue
|
|
|
|
|
|
|
|
if tag in keyMap:
|
|
|
|
sys.exit("Duplicate tag detected " + tag)
|
|
|
|
|
2013-08-08 16:13:29 +02:00
|
|
|
tag_path = os.path.expandvars(self.get(tag, option))
|
|
|
|
path = os.path.join(key_directory, tag_path)
|
2013-03-26 23:19:03 +01:00
|
|
|
|
|
|
|
keyMap[tag] = GenerateKeys(path)
|
|
|
|
|
|
|
|
# Multiple certificates may exist in
|
|
|
|
# the pem file. GenerateKeys supports
|
|
|
|
# this however, the mac_permissions.xml
|
|
|
|
# as well as PMS do not.
|
|
|
|
assert len(keyMap[tag]) == 1
|
|
|
|
|
|
|
|
return keyMap
|
|
|
|
|
|
|
|
class ReplaceTags(handler.ContentHandler):
|
|
|
|
|
|
|
|
DEFAULT_TAG = "default"
|
|
|
|
PACKAGE_TAG = "package"
|
|
|
|
POLICY_TAG = "policy"
|
|
|
|
SIGNER_TAG = "signer"
|
|
|
|
SIGNATURE_TAG = "signature"
|
|
|
|
|
|
|
|
TAGS_WITH_CHILDREN = [ DEFAULT_TAG, PACKAGE_TAG, POLICY_TAG, SIGNER_TAG ]
|
|
|
|
|
|
|
|
XML_ENCODING_TAG = '<?xml version="1.0" encoding="iso-8859-1"?>'
|
|
|
|
|
|
|
|
def __init__(self, keyMap, out=sys.stdout):
|
|
|
|
|
|
|
|
handler.ContentHandler.__init__(self)
|
|
|
|
self._keyMap = keyMap
|
|
|
|
self._out = out
|
|
|
|
self._out.write(ReplaceTags.XML_ENCODING_TAG)
|
|
|
|
self._out.write("<!-- AUTOGENERATED FILE DO NOT MODIFY -->")
|
2013-03-27 13:35:39 +01:00
|
|
|
self._out.write("<policy>")
|
|
|
|
|
|
|
|
def __del__(self):
|
|
|
|
self._out.write("</policy>")
|
2013-03-26 23:19:03 +01:00
|
|
|
|
|
|
|
def startElement(self, tag, attrs):
|
2013-03-27 13:35:39 +01:00
|
|
|
if tag == ReplaceTags.POLICY_TAG:
|
|
|
|
return
|
2013-03-26 23:19:03 +01:00
|
|
|
|
|
|
|
self._out.write('<' + tag)
|
|
|
|
|
|
|
|
for (name, value) in attrs.items():
|
|
|
|
|
|
|
|
if name == ReplaceTags.SIGNATURE_TAG and value in self._keyMap:
|
|
|
|
for key in self._keyMap[value].getBase16Keys():
|
|
|
|
logging.info("Replacing " + name + " " + value + " with " + key)
|
|
|
|
self._out.write(' %s="%s"' % (name, saxutils.escape(key)))
|
|
|
|
else:
|
|
|
|
self._out.write(' %s="%s"' % (name, saxutils.escape(value)))
|
|
|
|
|
|
|
|
if tag in ReplaceTags.TAGS_WITH_CHILDREN:
|
|
|
|
self._out.write('>')
|
|
|
|
else:
|
|
|
|
self._out.write('/>')
|
|
|
|
|
|
|
|
def endElement(self, tag):
|
2013-03-27 13:35:39 +01:00
|
|
|
if tag == ReplaceTags.POLICY_TAG:
|
|
|
|
return
|
|
|
|
|
2013-03-26 23:19:03 +01:00
|
|
|
if tag in ReplaceTags.TAGS_WITH_CHILDREN:
|
|
|
|
self._out.write('</%s>' % tag)
|
|
|
|
|
|
|
|
def characters(self, content):
|
|
|
|
if not content.isspace():
|
|
|
|
self._out.write(saxutils.escape(content))
|
|
|
|
|
|
|
|
def ignorableWhitespace(self, content):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def processingInstruction(self, target, data):
|
|
|
|
self._out.write('<?%s %s?>' % (target, data))
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
|
|
# Intentional double space to line up equls signs and opening " for
|
|
|
|
# readability.
|
2013-03-27 13:35:39 +01:00
|
|
|
usage = "usage: %prog [options] CONFIG_FILE MAC_PERMISSIONS_FILE [MAC_PERMISSIONS_FILE...]\n"
|
|
|
|
usage += "This tool allows one to configure an automatic inclusion\n"
|
|
|
|
usage += "of signing keys into the mac_permision.xml file(s) from the\n"
|
|
|
|
usage += "pem files. If mulitple mac_permision.xml files are included\n"
|
|
|
|
usage += "then they are unioned to produce a final version."
|
2013-03-26 23:19:03 +01:00
|
|
|
|
|
|
|
version = "%prog " + str(__VERSION)
|
|
|
|
|
|
|
|
parser = OptionParser(usage=usage, version=version)
|
|
|
|
|
|
|
|
parser.add_option("-v", "--verbose",
|
|
|
|
action="store_true", dest="verbose", default=False,
|
|
|
|
help="Print internal operations to stdout")
|
|
|
|
|
|
|
|
parser.add_option("-o", "--output", default="stdout", dest="output_file",
|
|
|
|
metavar="FILE", help="Specify an output file, default is stdout")
|
|
|
|
|
|
|
|
parser.add_option("-c", "--cwd", default=os.getcwd(), dest="root",
|
|
|
|
metavar="DIR", help="Specify a root (CWD) directory to run this from, it" \
|
|
|
|
"chdirs' AFTER loading the config file")
|
|
|
|
|
|
|
|
parser.add_option("-t", "--target-build-variant", default="eng", dest="target_build_variant",
|
|
|
|
help="Specify the TARGET_BUILD_VARIANT, defaults to eng")
|
|
|
|
|
2013-03-28 03:33:02 +01:00
|
|
|
parser.add_option("-d", "--key-directory", default="", dest="key_directory",
|
|
|
|
help="Specify a parent directory for keys")
|
|
|
|
|
2013-03-26 23:19:03 +01:00
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
|
2013-03-27 13:35:39 +01:00
|
|
|
if len(args) < 2:
|
|
|
|
parser.error("Must specify a config file (keys.conf) AND mac_permissions.xml file(s)!")
|
2013-03-26 23:19:03 +01:00
|
|
|
|
|
|
|
logging.basicConfig(level=logging.INFO if options.verbose == True else logging.WARN)
|
|
|
|
|
|
|
|
# Read the config file
|
|
|
|
config = ParseConfig()
|
|
|
|
config.read(args[0])
|
|
|
|
|
|
|
|
os.chdir(options.root)
|
|
|
|
|
|
|
|
output_file = sys.stdout if options.output_file == "stdout" else open(options.output_file, "w")
|
|
|
|
logging.info("Setting output file to: " + options.output_file)
|
|
|
|
|
|
|
|
# Generate the key list
|
2013-03-28 03:33:02 +01:00
|
|
|
key_map = config.generateKeyMap(options.target_build_variant.lower(), options.key_directory)
|
2013-03-26 23:19:03 +01:00
|
|
|
logging.info("Generate key map:")
|
|
|
|
for k in key_map:
|
|
|
|
logging.info(k + " : " + str(key_map[k]))
|
|
|
|
# Generate the XML file with markup replaced with keys
|
|
|
|
parser = make_parser()
|
|
|
|
parser.setContentHandler(ReplaceTags(key_map, output_file))
|
2013-03-27 13:35:39 +01:00
|
|
|
for f in args[1:]:
|
|
|
|
parser.parse(f)
|