summaryrefslogtreecommitdiffstats
path: root/mobile/android/debug_sign_tool.py
blob: 2d3e2099f80ae076613ae370f33edd30ce617b0f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env python

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
Sign Android packages using an Android debug keystore, creating the
keystore if it does not exist.

This and |zip| can be combined to replace the Android |apkbuilder|
tool, which was deprecated in SDK r22.

Exits with code 0 if creating the keystore and every signing succeeded,
or with code 1 if any creation or signing failed.
"""

from argparse import ArgumentParser
import errno
import logging
import os
import subprocess
import sys


log = logging.getLogger(os.path.basename(__file__))
log.setLevel(logging.INFO)
sh = logging.StreamHandler(stream=sys.stdout)
sh.setFormatter(logging.Formatter('%(name)s: %(message)s'))
log.addHandler(sh)


class DebugKeystore:
    """
    A thin abstraction on top of an Android debug key store.
    """
    def __init__(self, keystore):
        self._keystore = os.path.abspath(os.path.expanduser(keystore))
        self._alias = 'androiddebugkey'
        self.verbose = False
        self.keytool = 'keytool'
        self.jarsigner = 'jarsigner'

    @property
    def keystore(self):
        return self._keystore

    @property
    def alias(self):
        return self._alias

    def _check(self, args):
        try:
            if self.verbose:
                subprocess.check_call(args)
            else:
                subprocess.check_output(args)
        except OSError as ex:
            if ex.errno != errno.ENOENT:
                raise
            raise Exception("Could not find executable '%s'" % args[0])

    def keystore_contains_alias(self):
        args = [ self.keytool,
                 '-list',
                 '-keystore', self.keystore,
                 '-storepass', 'android',
                 '-alias', self.alias,
               ]
        if self.verbose:
            args.append('-v')
        contains = True
        try:
            self._check(args)
        except subprocess.CalledProcessError as e:
            contains = False
        if self.verbose:
            log.info('Keystore %s %s alias %s' %
                     (self.keystore,
                      'contains' if contains else 'does not contain',
                      self.alias))
        return contains

    def create_alias_in_keystore(self):
        try:
            path = os.path.dirname(self.keystore)
            os.makedirs(path)
        except OSError as exception:
            if exception.errno != errno.EEXIST:
                raise

        args = [ self.keytool,
                 '-genkeypair',
                 '-keystore', self.keystore,
                 '-storepass', 'android',
                 '-alias', self.alias,
                 '-keypass', 'android',
                 '-dname', 'CN=Android Debug,O=Android,C=US',
                 '-keyalg', 'RSA',
                 '-validity', '365',
               ]
        if self.verbose:
            args.append('-v')
        self._check(args)
        if self.verbose:
            log.info('Created alias %s in keystore %s' %
                     (self.alias, self.keystore))

    def sign(self, apk):
        if not self.keystore_contains_alias():
            self.create_alias_in_keystore()

        args = [ self.jarsigner,
                 '-digestalg', 'SHA1',
                 '-sigalg', 'MD5withRSA',
                 '-keystore', self.keystore,
                 '-storepass', 'android',
                 apk,
                 self.alias,
               ]
        if self.verbose:
            args.append('-verbose')
        self._check(args)
        if self.verbose:
            log.info('Signed %s with alias %s from keystore %s' %
                     (apk, self.alias, self.keystore))


def parse_args(argv):
    parser = ArgumentParser(description='Sign Android packages using an Android debug keystore.')
    parser.add_argument('apks', nargs='+',
                        metavar='APK',
                        help='Android packages to be signed')
    parser.add_argument('-v', '--verbose',
                        dest='verbose',
                        default=False,
                        action='store_true',
                        help='verbose output')
    parser.add_argument('--keytool',
                        metavar='PATH',
                        default='keytool',
                        help='path to Java keytool')
    parser.add_argument('--jarsigner',
                        metavar='PATH',
                        default='jarsigner',
                        help='path to Java jarsigner')
    parser.add_argument('--keystore',
                        metavar='PATH',
                        default='~/.android/debug.keystore',
                        help='path to keystore (default: ~/.android/debug.keystore)')
    parser.add_argument('-f', '--force-create-keystore',
                        dest='force',
                        default=False,
                        action='store_true',
                        help='force creating keystore')
    return parser.parse_args(argv)


def main():
    args = parse_args(sys.argv[1:])

    keystore = DebugKeystore(args.keystore)
    keystore.verbose = args.verbose
    keystore.keytool = args.keytool
    keystore.jarsigner = args.jarsigner

    if args.force:
        try:
            keystore.create_alias_in_keystore()
        except subprocess.CalledProcessError as e:
            log.error('Failed to force-create alias %s in keystore %s' %
                      (keystore.alias, keystore.keystore))
            log.error(e)
            return 1

    for apk in args.apks:
        try:
            keystore.sign(apk)
        except subprocess.CalledProcessError as e:
            log.error('Failed to sign %s', apk)
            log.error(e)
            return 1

    return 0

if __name__ == '__main__':
    sys.exit(main())