1 # Copyright 2014 Google Inc. All Rights Reserved.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 """Installs certificate on phone with KitKat."""
26 class CertInstallError(Exception):
29 class CertRemovalError(Exception):
33 class AndroidCertInstaller(object):
34 """Certificate installer for phones with KitKat."""
36 def __init__(self
, device_id
, cert_name
, cert_path
):
37 if not os
.path
.exists(cert_path
):
38 raise ValueError('Not a valid certificate path')
39 self
.device_id
= device_id
40 self
.cert_name
= cert_name
41 self
.cert_path
= cert_path
42 self
.file_name
= os
.path
.basename(self
.cert_path
)
43 self
.reformatted_cert_fname
= None
44 self
.reformatted_cert_path
= None
45 self
.android_cacerts_path
= None
48 def _run_cmd(cmd
, dirname
=None):
49 return subprocess
.check_output(cmd
, cwd
=dirname
)
51 def _adb(self
, *args
):
52 """Runs the adb command."""
55 cmd
.extend(['-s', self
.device_id
])
57 return self
._run
_cmd
(cmd
)
59 def _adb_su_shell(self
, *args
):
60 """Runs command as root."""
61 cmd
= ['shell', 'su', '-c']
63 return self
._adb
(*cmd
)
65 def _get_property(self
, prop
):
66 return self
._adb
('shell', 'getprop', prop
).strip()
68 def check_device(self
):
69 install_warning
= False
70 if self
._get
_property
('ro.product.device') != 'hammerhead':
71 logging
.warning('Device is not hammerhead')
72 install_warning
= True
73 if self
._get
_property
('ro.build.version.release') != '4.4.2':
74 logging
.warning('Version is not 4.4.2')
75 install_warning
= True
77 logging
.warning('Certificate may not install properly')
79 def _input_key(self
, key
):
80 """Inputs a keyevent."""
81 self
._adb
('shell', 'input', 'keyevent', key
)
83 def _input_text(self
, text
):
85 self
._adb
('shell', 'input', 'text', text
)
88 def _remove(file_name
):
90 if os
.path
.exists(file_name
):
93 def _format_hashed_cert(self
):
94 """Makes a certificate file that follows the format of files in cacerts."""
95 self
._remove
(self
.reformatted_cert_path
)
96 contents
= self
._run
_cmd
(['openssl', 'x509', '-inform', 'PEM', '-text',
97 '-in', self
.cert_path
])
98 description
, begin_cert
, cert_body
= contents
.rpartition('-----BEGIN '
100 contents
= ''.join([begin_cert
, cert_body
, description
])
101 with
open(self
.reformatted_cert_path
, 'w') as cert_file
:
102 cert_file
.write(contents
)
104 def _remove_cert_from_cacerts(self
):
105 self
._adb
_su
_shell
('mount', '-o', 'remount,rw', '/system')
106 self
._adb
_su
_shell
('rm', self
.android_cacerts_path
)
108 def _is_cert_installed(self
):
109 return (self
._adb
_su
_shell
('ls', self
.android_cacerts_path
).strip() ==
110 self
.android_cacerts_path
)
112 def _generate_reformatted_cert_path(self
):
113 # Determine OpenSSL version, string is of the form
114 # 'OpenSSL 0.9.8za 5 Jun 2014' .
115 openssl_version
= self
._run
_cmd
(['openssl', 'version']).split()
117 if len(openssl_version
) < 2:
118 raise ValueError('Unexpected OpenSSL version string: ', openssl_version
)
120 # subject_hash flag name changed as of OpenSSL version 1.0.0 .
121 is_old_openssl_version
= openssl_version
[1].startswith('0')
122 subject_hash_flag
= (
123 '-subject_hash' if is_old_openssl_version
else '-subject_hash_old')
125 output
= self
._run
_cmd
(['openssl', 'x509', '-inform', 'PEM',
126 subject_hash_flag
, '-in', self
.cert_path
],
127 os
.path
.dirname(self
.cert_path
))
128 self
.reformatted_cert_fname
= output
.partition('\n')[0].strip() + '.0'
129 self
.reformatted_cert_path
= os
.path
.join(os
.path
.dirname(self
.cert_path
),
130 self
.reformatted_cert_fname
)
131 self
.android_cacerts_path
= ('/system/etc/security/cacerts/%s' %
132 self
.reformatted_cert_fname
)
134 def remove_cert(self
):
135 self
._generate
_reformatted
_cert
_path
()
137 if self
._is
_cert
_installed
():
138 self
._remove
_cert
_from
_cacerts
()
140 if self
._is
_cert
_installed
():
141 raise CertRemovalError('Cert Removal Failed')
143 def install_cert(self
, overwrite_cert
=False):
144 """Installs a certificate putting it in /system/etc/security/cacerts."""
145 self
._generate
_reformatted
_cert
_path
()
147 if self
._is
_cert
_installed
():
149 self
._remove
_cert
_from
_cacerts
()
151 logging
.info('cert is already installed')
154 self
._format
_hashed
_cert
()
155 self
._adb
('push', self
.reformatted_cert_path
, '/sdcard/')
156 self
._remove
(self
.reformatted_cert_path
)
157 self
._adb
_su
_shell
('mount', '-o', 'remount,rw', '/system')
159 'cp', '/sdcard/%s' % self
.reformatted_cert_fname
,
160 '/system/etc/security/cacerts/%s' % self
.reformatted_cert_fname
)
161 self
._adb
_su
_shell
('chmod', '644', self
.android_cacerts_path
)
162 if not self
._is
_cert
_installed
():
163 raise CertInstallError('Cert Install Failed')
165 def install_cert_using_gui(self
):
166 """Installs certificate on the device using adb commands."""
168 # TODO(mruthven): Add a check to see if the certificate is already installed
169 # Install the certificate.
170 logging
.info('Installing %s on %s', self
.cert_path
, self
.device_id
)
171 self
._adb
('push', self
.cert_path
, '/sdcard/')
173 # Start credential install intent.
174 self
._adb
('shell', 'am', 'start', '-W', '-a', 'android.credentials.INSTALL')
176 # Move to and click search button.
177 self
._input
_key
(KEYCODE_TAB
)
178 self
._input
_key
(KEYCODE_TAB
)
179 self
._input
_key
(KEYCODE_ENTER
)
181 # Search for certificate and click it.
182 # Search only works with lower case letters
183 self
._input
_text
(self
.file_name
.lower())
184 self
._input
_key
(KEYCODE_ENTER
)
186 # These coordinates work for hammerhead devices.
187 self
._adb
('shell', 'input', 'tap', '300', '300')
189 # Name the certificate and click enter.
190 self
._input
_text
(self
.cert_name
)
191 self
._input
_key
(KEYCODE_TAB
)
192 self
._input
_key
(KEYCODE_TAB
)
193 self
._input
_key
(KEYCODE_TAB
)
194 self
._input
_key
(KEYCODE_ENTER
)
197 self
._adb
('shell', 'rm', '/sdcard/' + self
.file_name
)
201 """Parses command line arguments."""
202 parser
= argparse
.ArgumentParser(description
='Install cert on device.')
204 '-n', '--cert-name', default
='dummycert', help='certificate name')
206 '--overwrite', default
=False, action
='store_true',
207 help='Overwrite certificate file if it is already installed')
209 '--remove', default
=False, action
='store_true',
210 help='Remove certificate file if it is installed')
212 '--device-id', help='device serial number')
214 'cert_path', help='Certificate file path')
215 return parser
.parse_args()
220 cert_installer
= AndroidCertInstaller(args
.device_id
, args
.cert_name
,
223 cert_installer
.remove_cert()
225 cert_installer
.install_cert(args
.overwrite
)
228 if __name__
== '__main__':