michael@0: #!/usr/bin/env python michael@0: michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: """ michael@0: Sign Android packages using an Android debug keystore, creating the michael@0: keystore if it does not exist. michael@0: michael@0: This and |zip| can be combined to replace the Android |apkbuilder| michael@0: tool, which was deprecated in SDK r22. michael@0: michael@0: Exits with code 0 if creating the keystore and every signing succeeded, michael@0: or with code 1 if any creation or signing failed. michael@0: """ michael@0: michael@0: from argparse import ArgumentParser michael@0: import errno michael@0: import logging michael@0: import os michael@0: import subprocess michael@0: import sys michael@0: michael@0: michael@0: log = logging.getLogger(os.path.basename(__file__)) michael@0: log.setLevel(logging.INFO) michael@0: sh = logging.StreamHandler(stream=sys.stdout) michael@0: sh.setFormatter(logging.Formatter('%(name)s: %(message)s')) michael@0: log.addHandler(sh) michael@0: michael@0: michael@0: class DebugKeystore: michael@0: """ michael@0: A thin abstraction on top of an Android debug key store. michael@0: """ michael@0: def __init__(self, keystore): michael@0: self._keystore = os.path.abspath(os.path.expanduser(keystore)) michael@0: self._alias = 'androiddebugkey' michael@0: self.verbose = False michael@0: self.keytool = 'keytool' michael@0: self.jarsigner = 'jarsigner' michael@0: michael@0: @property michael@0: def keystore(self): michael@0: return self._keystore michael@0: michael@0: @property michael@0: def alias(self): michael@0: return self._alias michael@0: michael@0: def _check(self, args): michael@0: try: michael@0: if self.verbose: michael@0: subprocess.check_call(args) michael@0: else: michael@0: subprocess.check_output(args) michael@0: except OSError as ex: michael@0: if ex.errno != errno.ENOENT: michael@0: raise michael@0: raise Exception("Could not find executable '%s'" % args[0]) michael@0: michael@0: def keystore_contains_alias(self): michael@0: args = [ self.keytool, michael@0: '-list', michael@0: '-keystore', self.keystore, michael@0: '-storepass', 'android', michael@0: '-alias', self.alias, michael@0: ] michael@0: if self.verbose: michael@0: args.append('-v') michael@0: contains = True michael@0: try: michael@0: self._check(args) michael@0: except subprocess.CalledProcessError as e: michael@0: contains = False michael@0: if self.verbose: michael@0: log.info('Keystore %s %s alias %s' % michael@0: (self.keystore, michael@0: 'contains' if contains else 'does not contain', michael@0: self.alias)) michael@0: return contains michael@0: michael@0: def create_alias_in_keystore(self): michael@0: try: michael@0: path = os.path.dirname(self.keystore) michael@0: os.makedirs(path) michael@0: except OSError as exception: michael@0: if exception.errno != errno.EEXIST: michael@0: raise michael@0: michael@0: args = [ self.keytool, michael@0: '-genkeypair', michael@0: '-keystore', self.keystore, michael@0: '-storepass', 'android', michael@0: '-alias', self.alias, michael@0: '-keypass', 'android', michael@0: '-dname', 'CN=Android Debug,O=Android,C=US', michael@0: '-keyalg', 'RSA', michael@0: '-validity', '365', michael@0: ] michael@0: if self.verbose: michael@0: args.append('-v') michael@0: self._check(args) michael@0: if self.verbose: michael@0: log.info('Created alias %s in keystore %s' % michael@0: (self.alias, self.keystore)) michael@0: michael@0: def sign(self, apk): michael@0: if not self.keystore_contains_alias(): michael@0: self.create_alias_in_keystore() michael@0: michael@0: args = [ self.jarsigner, michael@0: '-digestalg', 'SHA1', michael@0: '-sigalg', 'MD5withRSA', michael@0: '-keystore', self.keystore, michael@0: '-storepass', 'android', michael@0: apk, michael@0: self.alias, michael@0: ] michael@0: if self.verbose: michael@0: args.append('-verbose') michael@0: self._check(args) michael@0: if self.verbose: michael@0: log.info('Signed %s with alias %s from keystore %s' % michael@0: (apk, self.alias, self.keystore)) michael@0: michael@0: michael@0: def parse_args(argv): michael@0: parser = ArgumentParser(description='Sign Android packages using an Android debug keystore.') michael@0: parser.add_argument('apks', nargs='+', michael@0: metavar='APK', michael@0: help='Android packages to be signed') michael@0: parser.add_argument('-v', '--verbose', michael@0: dest='verbose', michael@0: default=False, michael@0: action='store_true', michael@0: help='verbose output') michael@0: parser.add_argument('--keytool', michael@0: metavar='PATH', michael@0: default='keytool', michael@0: help='path to Java keytool') michael@0: parser.add_argument('--jarsigner', michael@0: metavar='PATH', michael@0: default='jarsigner', michael@0: help='path to Java jarsigner') michael@0: parser.add_argument('--keystore', michael@0: metavar='PATH', michael@0: default='~/.android/debug.keystore', michael@0: help='path to keystore (default: ~/.android/debug.keystore)') michael@0: parser.add_argument('-f', '--force-create-keystore', michael@0: dest='force', michael@0: default=False, michael@0: action='store_true', michael@0: help='force creating keystore') michael@0: return parser.parse_args(argv) michael@0: michael@0: michael@0: def main(): michael@0: args = parse_args(sys.argv[1:]) michael@0: michael@0: keystore = DebugKeystore(args.keystore) michael@0: keystore.verbose = args.verbose michael@0: keystore.keytool = args.keytool michael@0: keystore.jarsigner = args.jarsigner michael@0: michael@0: if args.force: michael@0: try: michael@0: keystore.create_alias_in_keystore() michael@0: except subprocess.CalledProcessError as e: michael@0: log.error('Failed to force-create alias %s in keystore %s' % michael@0: (keystore.alias, keystore.keystore)) michael@0: log.error(e) michael@0: return 1 michael@0: michael@0: for apk in args.apks: michael@0: try: michael@0: keystore.sign(apk) michael@0: except subprocess.CalledProcessError as e: michael@0: log.error('Failed to sign %s', apk) michael@0: log.error(e) michael@0: return 1 michael@0: michael@0: return 0 michael@0: michael@0: if __name__ == '__main__': michael@0: sys.exit(main())