1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/debug_sign_tool.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,187 @@ 1.4 +#!/usr/bin/env python 1.5 + 1.6 +# This Source Code Form is subject to the terms of the Mozilla Public 1.7 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.9 + 1.10 +""" 1.11 +Sign Android packages using an Android debug keystore, creating the 1.12 +keystore if it does not exist. 1.13 + 1.14 +This and |zip| can be combined to replace the Android |apkbuilder| 1.15 +tool, which was deprecated in SDK r22. 1.16 + 1.17 +Exits with code 0 if creating the keystore and every signing succeeded, 1.18 +or with code 1 if any creation or signing failed. 1.19 +""" 1.20 + 1.21 +from argparse import ArgumentParser 1.22 +import errno 1.23 +import logging 1.24 +import os 1.25 +import subprocess 1.26 +import sys 1.27 + 1.28 + 1.29 +log = logging.getLogger(os.path.basename(__file__)) 1.30 +log.setLevel(logging.INFO) 1.31 +sh = logging.StreamHandler(stream=sys.stdout) 1.32 +sh.setFormatter(logging.Formatter('%(name)s: %(message)s')) 1.33 +log.addHandler(sh) 1.34 + 1.35 + 1.36 +class DebugKeystore: 1.37 + """ 1.38 + A thin abstraction on top of an Android debug key store. 1.39 + """ 1.40 + def __init__(self, keystore): 1.41 + self._keystore = os.path.abspath(os.path.expanduser(keystore)) 1.42 + self._alias = 'androiddebugkey' 1.43 + self.verbose = False 1.44 + self.keytool = 'keytool' 1.45 + self.jarsigner = 'jarsigner' 1.46 + 1.47 + @property 1.48 + def keystore(self): 1.49 + return self._keystore 1.50 + 1.51 + @property 1.52 + def alias(self): 1.53 + return self._alias 1.54 + 1.55 + def _check(self, args): 1.56 + try: 1.57 + if self.verbose: 1.58 + subprocess.check_call(args) 1.59 + else: 1.60 + subprocess.check_output(args) 1.61 + except OSError as ex: 1.62 + if ex.errno != errno.ENOENT: 1.63 + raise 1.64 + raise Exception("Could not find executable '%s'" % args[0]) 1.65 + 1.66 + def keystore_contains_alias(self): 1.67 + args = [ self.keytool, 1.68 + '-list', 1.69 + '-keystore', self.keystore, 1.70 + '-storepass', 'android', 1.71 + '-alias', self.alias, 1.72 + ] 1.73 + if self.verbose: 1.74 + args.append('-v') 1.75 + contains = True 1.76 + try: 1.77 + self._check(args) 1.78 + except subprocess.CalledProcessError as e: 1.79 + contains = False 1.80 + if self.verbose: 1.81 + log.info('Keystore %s %s alias %s' % 1.82 + (self.keystore, 1.83 + 'contains' if contains else 'does not contain', 1.84 + self.alias)) 1.85 + return contains 1.86 + 1.87 + def create_alias_in_keystore(self): 1.88 + try: 1.89 + path = os.path.dirname(self.keystore) 1.90 + os.makedirs(path) 1.91 + except OSError as exception: 1.92 + if exception.errno != errno.EEXIST: 1.93 + raise 1.94 + 1.95 + args = [ self.keytool, 1.96 + '-genkeypair', 1.97 + '-keystore', self.keystore, 1.98 + '-storepass', 'android', 1.99 + '-alias', self.alias, 1.100 + '-keypass', 'android', 1.101 + '-dname', 'CN=Android Debug,O=Android,C=US', 1.102 + '-keyalg', 'RSA', 1.103 + '-validity', '365', 1.104 + ] 1.105 + if self.verbose: 1.106 + args.append('-v') 1.107 + self._check(args) 1.108 + if self.verbose: 1.109 + log.info('Created alias %s in keystore %s' % 1.110 + (self.alias, self.keystore)) 1.111 + 1.112 + def sign(self, apk): 1.113 + if not self.keystore_contains_alias(): 1.114 + self.create_alias_in_keystore() 1.115 + 1.116 + args = [ self.jarsigner, 1.117 + '-digestalg', 'SHA1', 1.118 + '-sigalg', 'MD5withRSA', 1.119 + '-keystore', self.keystore, 1.120 + '-storepass', 'android', 1.121 + apk, 1.122 + self.alias, 1.123 + ] 1.124 + if self.verbose: 1.125 + args.append('-verbose') 1.126 + self._check(args) 1.127 + if self.verbose: 1.128 + log.info('Signed %s with alias %s from keystore %s' % 1.129 + (apk, self.alias, self.keystore)) 1.130 + 1.131 + 1.132 +def parse_args(argv): 1.133 + parser = ArgumentParser(description='Sign Android packages using an Android debug keystore.') 1.134 + parser.add_argument('apks', nargs='+', 1.135 + metavar='APK', 1.136 + help='Android packages to be signed') 1.137 + parser.add_argument('-v', '--verbose', 1.138 + dest='verbose', 1.139 + default=False, 1.140 + action='store_true', 1.141 + help='verbose output') 1.142 + parser.add_argument('--keytool', 1.143 + metavar='PATH', 1.144 + default='keytool', 1.145 + help='path to Java keytool') 1.146 + parser.add_argument('--jarsigner', 1.147 + metavar='PATH', 1.148 + default='jarsigner', 1.149 + help='path to Java jarsigner') 1.150 + parser.add_argument('--keystore', 1.151 + metavar='PATH', 1.152 + default='~/.android/debug.keystore', 1.153 + help='path to keystore (default: ~/.android/debug.keystore)') 1.154 + parser.add_argument('-f', '--force-create-keystore', 1.155 + dest='force', 1.156 + default=False, 1.157 + action='store_true', 1.158 + help='force creating keystore') 1.159 + return parser.parse_args(argv) 1.160 + 1.161 + 1.162 +def main(): 1.163 + args = parse_args(sys.argv[1:]) 1.164 + 1.165 + keystore = DebugKeystore(args.keystore) 1.166 + keystore.verbose = args.verbose 1.167 + keystore.keytool = args.keytool 1.168 + keystore.jarsigner = args.jarsigner 1.169 + 1.170 + if args.force: 1.171 + try: 1.172 + keystore.create_alias_in_keystore() 1.173 + except subprocess.CalledProcessError as e: 1.174 + log.error('Failed to force-create alias %s in keystore %s' % 1.175 + (keystore.alias, keystore.keystore)) 1.176 + log.error(e) 1.177 + return 1 1.178 + 1.179 + for apk in args.apks: 1.180 + try: 1.181 + keystore.sign(apk) 1.182 + except subprocess.CalledProcessError as e: 1.183 + log.error('Failed to sign %s', apk) 1.184 + log.error(e) 1.185 + return 1 1.186 + 1.187 + return 0 1.188 + 1.189 +if __name__ == '__main__': 1.190 + sys.exit(main())