Revert "Dynamic insertion of pubkey to mac_permissions.xml"

This reverts commit 22fc04103b

Change-Id: I2d91b1262e8d0e82a21ea7c5333b1e86f3ed9bee
This commit is contained in:
Geremy Condra 2013-03-19 22:56:32 +00:00 committed by Gerrit Code Review
parent 5a2988fcb5
commit 1446e714af
6 changed files with 7 additions and 306 deletions

View file

@ -165,23 +165,12 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/security
include $(BUILD_SYSTEM)/base_rules.mk
# Build keys.conf
mac_perms_keys.tmp := $(intermediates)/keys.tmp
$(mac_perms_keys.tmp) : $(call build_policy, keys.conf)
@mkdir -p $(dir $@)
$(hide) m4 -s $^ > $@
# Build mac_permissions.xml
$(MAC_PERMISSION_FILE).tmp := $(intermediates)/$(MAC_PERMISSION_FILE).tmp
$($(MAC_PERMISSION_FILE).tmp) : $(call build_policy, $(MAC_PERMISSION_FILE))
mmac := $(intermediates)/$(MAC_PERMISSION_FILE)
$(mmac) : $(call build_policy, $(MAC_PERMISSION_FILE))
@mkdir -p $(dir $@)
$(hide) cp $^ $@
$(LOCAL_BUILT_MODULE) : $($(MAC_PERMISSION_FILE).tmp) $(mac_perms_keys.tmp) $(HOST_OUT_EXECUTABLES)/insertkeys.py
@mkdir -p $(dir $@)
$(HOST_OUT_EXECUTABLES)/insertkeys.py -t $(TARGET_BUILD_VARIANT) -c $(ANDROID_BUILD_TOP) $(mac_perms_keys.tmp) -o $@ $<
$(MAC_PERMISSION_FILE).tmp :=
mmac :=
##################################
build_policy :=

44
README
View file

@ -56,47 +56,3 @@ BOARD_SEPOLICY_UNION := \
genfs_contexts \
file_contexts \
sepolicy.te
SPECIFIC POLICY FILE INFORMATION
mac_permissions.xml:
ABOUT:
The mac_permissions.xml file is used for controlling the mmac solutions
as well as mapping a public base16 signing key with an arbitrary seinfo
string. Details of the files contents can be found in a comment at the
top of that file. The seinfo string, previously mentioned, is the same string
that is referenced in seapp_contexts.
This file can be replaced through BOARD_SEPOLICY_REPLACE containing the
value "mac_permissions.xml", however, appending (UNION) does NOT exist
and will cause a build time failure. It is important to note the final
processed version of this file is stripped of comments and whitespace.
This is to preserve space on the system.img. If one wishes to view it in
a more human friendly format, the "tidy" or "xmllint" command will assist
you.
TOOLING:
insertkeys.py
Is a helper script for mapping arbitrary tags in the signature stanzas of
mac_permissions.xml to public keys found in pem files. This script takes
a mac_permissions.xml file and configuration file in order to operate.
Details of the configuration file (keys.conf) can be found in the subsection
keys.conf. This script is also responsible for stipping the comments and
whitespace from the xml file.
keys.conf
The keys.conf file is used for controlling the mapping of "tags" found in
the mac_permissions.xml signature stanzas with actual public keys found in
pem files. The configuration file can be used in BOARD_SEPOLICY_UNION and
BOARD_SEPOLICY_REPLACE variables and is processed via m4.
The script allows for mapping any string contained in TARGET_BUILD_VARIANT
with specific path to a pem file. Typically TARGET_BUILD_VARIANT is either
user, eng or userdebug. Additionally, one can specify "ALL" to map a path to
any string specified in TARGET_BUILD_VARIANT. All tags are matched verbatim
and all options are matched lowercase. The options are "tolowered" automatically
for the user, it is convention to specify tags and options in all uppercase
and tags start with @.
NOTE: The pem files are base64 encoded and PackageManagerService, mac_permissions.xml
and setool all use base16 encodings.

View file

@ -1,25 +0,0 @@
#
# Maps an arbitrary tag [TAGNAME] with the string contents found in
# TARGET_BUILD_VARAINT. Common convention is to start TAGNAME with an @ and
# name it after the base file name of the pem file.
#
# Each tag (section) then allows one to specify any string found in
# TARGET_BUILD_VARIANT. Typcially this is user, eng, and userdebug. Another
# option is to use ALL which will match ANY TARGET_BUILD_VARAINT string.
#
[@PLATFORM]
ALL : build/target/product/security/platform.x509.pem
[@MEDIA]
ALL : build/target/product/security/media.x509.pem
[@SHARED]
ALL : build/target/product/security/shared.x509.pem
# Example of ALL TARGET_BUILD_VARIANTS
[@RELEASE]
ENG : build/target/product/security/testkey.x509.pem
USER : build/target/product/security/testkey.x509.pem
USERDEBUG : build/target/product/security/testkey.x509.pem

View file

@ -64,13 +64,13 @@
-->
<!-- Platform dev key with AOSP -->
<signer signature="@PLATFORM" >
<signer signature="308204a830820390a003020102020900b3998086d056cffa300d06092a864886f70d0101040500308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d301e170d3038303431353232343035305a170d3335303930313232343035305a308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d30820120300d06092a864886f70d01010105000382010d003082010802820101009c780592ac0d5d381cdeaa65ecc8a6006e36480c6d7207b12011be50863aabe2b55d009adf7146d6f2202280c7cd4d7bdb26243b8a806c26b34b137523a49268224904dc01493e7c0acf1a05c874f69b037b60309d9074d24280e16bad2a8734361951eaf72a482d09b204b1875e12ac98c1aa773d6800b9eafde56d58bed8e8da16f9a360099c37a834a6dfedb7b6b44a049e07a269fccf2c5496f2cf36d64df90a3b8d8f34a3baab4cf53371ab27719b3ba58754ad0c53fc14e1db45d51e234fbbe93c9ba4edf9ce54261350ec535607bf69a2ff4aa07db5f7ea200d09a6c1b49e21402f89ed1190893aab5a9180f152e82f85a45753cf5fc19071c5eec827020103a381fc3081f9301d0603551d0e041604144fe4a0b3dd9cba29f71d7287c4e7c38f2086c2993081c90603551d230481c13081be80144fe4a0b3dd9cba29f71d7287c4e7c38f2086c299a1819aa48197308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d820900b3998086d056cffa300c0603551d13040530030101ff300d06092a864886f70d01010405000382010100572551b8d93a1f73de0f6d469f86dad6701400293c88a0cd7cd778b73dafcc197fab76e6212e56c1c761cfc42fd733de52c50ae08814cefc0a3b5a1a4346054d829f1d82b42b2048bf88b5d14929ef85f60edd12d72d55657e22e3e85d04c831d613d19938bb8982247fa321256ba12d1d6a8f92ea1db1c373317ba0c037f0d1aff645aef224979fba6e7a14bc025c71b98138cef3ddfc059617cf24845cf7b40d6382f7275ed738495ab6e5931b9421765c491b72fb68e080dbdb58c2029d347c8b328ce43ef6a8b15533edfbe989bd6a48dd4b202eda94c6ab8dd5b8399203daae2ed446232e4fe9bd961394c6300e5138e3cfd285e6e4e483538cb8b1b357" >
<allow-all />
<seinfo value="platform" />
</signer>
<!-- Media dev key in AOSP -->
<signer signature="@MEDIA" >
<signer signature="308204a830820390a003020102020900f2b98e6123572c4e300d06092a864886f70d0101040500308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d301e170d3038303431353233343035375a170d3335303930313233343035375a308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d30820120300d06092a864886f70d01010105000382010d00308201080282010100ae250c5a16ef97fc2869ac651b3217cc36ba0e86964168d58a049f40ce85867123a3ffb4f6d949c33cf2da3a05c23eacaa57d803889b1759bcf59e7c6f21890ae25085b7ed56aa626c0989ef9ccd36362ca0e8d1b9603fd4d8328767926ccc090c68b775ae7ff30934cc369ef2855a2667df0c667fd0c7cf5d8eba655806737303bb624726eabaedfb72f07ed7a76ab3cb9a381c4b7dcd809b140d891f00213be401f58d6a06a61eadc3a9c2f1c6567285b09ae09342a66fa421eaf93adf7573a028c331d70601ab3af7cc84033ece7c772a3a5b86b0dbe9d777c3a48aa9801edcee2781589f44d9e4113979600576a99410ba81091259dad98c6c68ff784b8f020103a381fc3081f9301d0603551d0e04160414ca293caa8bc0ed3e542eef4205a2bff2b57e4d753081c90603551d230481c13081be8014ca293caa8bc0ed3e542eef4205a2bff2b57e4d75a1819aa48197308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d820900f2b98e6123572c4e300c0603551d13040530030101ff300d06092a864886f70d0101040500038201010084de9516d5e4a87217a73da8487048f53373a5f733f390d61bdf3cc9e5251625bfcaa7c3159cae275d172a9ae1e876d5458127ac542f68290dd510c0029d8f51e0ee156b7b7b5acdb394241b8ec78b74e5c42c5cafae156caf5bd199a23a27524da072debbe378464a533630b0e4d0ffb7e08ecb701fadb6379c74467f6e00c6ed888595380792038756007872c8e3007af423a57a2cab3a282869b64c4b7bd5fc187d0a7e2415965d5aae4e07a6df751b4a75e9793c918a612b81cd0b628aee0168dc44e47b10d3593260849d6adf6d727dc24444c221d3f9ecc368cad07999f2b8105bc1f20d38d41066cc1411c257a96ea4349f5746565507e4e8020a1a81" >
<allow-permission name="android.permission.ACCESS_ALL_DOWNLOADS" />
<allow-permission name="android.permission.ACCESS_CACHE_FILESYSTEM" />
<allow-permission name="android.permission.ACCESS_DOWNLOAD_MANAGER" />
@ -92,7 +92,7 @@
</signer>
<!-- shared dev key in AOSP -->
<signer signature="@SHARED" >
<signer signature="308204a830820390a003020102020900f2a73396bd38767a300d06092a864886f70d0101040500308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d301e170d3038303732333231353735395a170d3335313230393231353735395a308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d30820120300d06092a864886f70d01010105000382010d00308201080282010100c8c2dbfd094a2df45c3ff1a32ed21805ec72fc58d017971bd0f6b52c262d70819d191967e158dfd3a2c7f1b3e0e80ce545d79d2848220211eb86f0fd8312d37b420c113750cc94618ae872f4886463bdc4627caa0c0483c86493e3515571170338bfdcc4cd6addd1c0a2f35f5cf24ed3e4043a3e58e2b05e664ccde12bcb67735fd6df1249c369e62542bc0a4729e53917f5c38ffa52d17b73c9c73798ddb18ed481590875547e66bfc5daca4c25a6eb960ed96923709da302ba646cb496b325e86c5c8b2e7a3377b2bbe4c7cf33254291163f689152ac088550c83c508f4bf5adf0aed5a2dca0583f9ab0ad17650db7eea4b23fdb45885547d0feab72183889020103a381fc3081f9301d0603551d0e04160414cb4c7e2cdbb3f0ada98dab79968d172e9dbb1ed13081c90603551d230481c13081be8014cb4c7e2cdbb3f0ada98dab79968d172e9dbb1ed1a1819aa48197308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d820900f2a73396bd38767a300c0603551d13040530030101ff300d06092a864886f70d0101040500038201010040a8d096997959e917a36c44246b6bac2bae05437ecd89794118f7834720352d1c6f8a39b0869942f4da65981faa2951d33971129ec1921d795671c527d6e249f252829faf5b591310311e2de096500d568ad4114a656dc34a8c6f610453afc1ea7992dba4aa7b3f8543a6e35c0728de77fe97eeac83771fd0ec90f8e4449434ee0b6045783e70c7a2e460249260e003cf7608dc352a4c9ef706def4b26050e978ae2fffd7a3323787014915eb3cc874fcc7a9ae930877c5c8c7d1c2e2a8ee863c89180d1855cedba400e7ba43cccaa7243d397e7c0e8e8e4d7d4f92b6bbead49c0cf018069eddca2e7e2fb4668d89dbbd7950d0cd254180fa1eaafc2a556f84" >
<allow-permission name="android.permission.ACCESS_COARSE_LOCATION" />
<allow-permission name="android.permission.ACCESS_FINE_LOCATION" />
<allow-permission name="android.permission.ACCESS_NETWORK_STATE" />
@ -149,7 +149,7 @@
</signer>
<!-- release dev key in AOSP -->
<signer signature="@RELEASE" >
<signer signature="308204a830820390a003020102020900936eacbe07f201df300d06092a864886f70d0101050500308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d301e170d3038303232393031333334365a170d3335303731373031333334365a308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d30820120300d06092a864886f70d01010105000382010d00308201080282010100d6931904dec60b24b1edc762e0d9d8253e3ecd6ceb1de2ff068ca8e8bca8cd6bd3786ea70aa76ce60ebb0f993559ffd93e77a943e7e83d4b64b8e4fea2d3e656f1e267a81bbfb230b578c20443be4c7218b846f5211586f038a14e89c2be387f8ebecf8fcac3da1ee330c9ea93d0a7c3dc4af350220d50080732e0809717ee6a053359e6a694ec2cb3f284a0a466c87a94d83b31093a67372e2f6412c06e6d42f15818dffe0381cc0cd444da6cddc3b82458194801b32564134fbfde98c9287748dbf5676a540d8154c8bbca07b9e247553311c46b9af76fdeeccc8e69e7c8a2d08e782620943f99727d3c04fe72991d99df9bae38a0b2177fa31d5b6afee91f020103a381fc3081f9301d0603551d0e04160414485900563d272c46ae118605a47419ac09ca8c113081c90603551d230481c13081be8014485900563d272c46ae118605a47419ac09ca8c11a1819aa48197308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d820900936eacbe07f201df300c0603551d13040530030101ff300d06092a864886f70d010105050003820101007aaf968ceb50c441055118d0daabaf015b8a765a27a715a2c2b44f221415ffdace03095abfa42df70708726c2069e5c36eddae0400be29452c084bc27eb6a17eac9dbe182c204eb15311f455d824b656dbe4dc2240912d7586fe88951d01a8feb5ae5a4260535df83431052422468c36e22c2a5ef994d61dd7306ae4c9f6951ba3c12f1d1914ddc61f1a62da2df827f603fea5603b2c540dbd7c019c36bab29a4271c117df523cdbc5f3817a49e0efa60cbd7f74177e7a4f193d43f4220772666e4c4d83e1bd5a86087cf34f2dec21e245ca6c2bb016e683638050d2c430eea7c26a1c49d3760a58ab7f1a82cc938b4831384324bd0401fa12163a50570e684d" >
<seinfo value="release" />
<deny-permission name="android.permission.BRICK" />
<deny-permission name="android.permission.READ_LOGS" />

View file

@ -22,14 +22,3 @@ LOCAL_SRC_FILES := checkfc.c
LOCAL_STATIC_LIBRARIES := libsepol libselinux
include $(BUILD_HOST_EXECUTABLE)
##################################
include $(CLEAR_VARS)
LOCAL_MODULE := insertkeys.py
LOCAL_SRC_FILES := insertkeys.py
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_IS_HOST_MODULE := true
LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)

View file

@ -1,208 +0,0 @@
#!/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 = ""
inCert = False
for line in pkFile:
if line.startswith("-"):
inCert = not inCert
continue
base64Key += line.strip()
# Base 64 includes uppercase. DO NOT tolower()
self._base64Key.append(base64Key)
# Pkgmanager and setool see hex strings with lowercase, lets be consistent.
self._base16Key.append(base64.b16encode(base64.b64decode(base64Key)).lower())
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"
def generateKeyMap(self, target_build_variant):
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)
path = self.get(tag, option)
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
def startDocument(self):
self._out.write(ReplaceTags.XML_ENCODING_TAG)
self._out.write("<!-- AUTOGENERATED FILE DO NOT MODIFY -->")
def startElement(self, tag, attrs):
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):
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.
usage = "usage: %prog [options] CONFIG_FILE MAC_PERMISSIONS_FILE\n"
usage += "This tool allows one to configure an automatic inclusion "
usage += "of signing keys into the mac_permision.xml file from the "
usage += "pem files."
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")
(options, args) = parser.parse_args()
if len(args) != 2:
parser.error("Must specify a config file (keys.conf) AND mac_permissions.xml file!")
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
key_map = config.generateKeyMap(options.target_build_variant.lower())
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))
parser.parse(args[1])