xref: /aosp_15_r20/external/avb/tools/at_auth_unlock.py (revision d289c2ba6de359471b23d594623b906876bc48a0)
1*d289c2baSAndroid Build Coastguard Worker#!/usr/bin/env python
2*d289c2baSAndroid Build Coastguard Worker#
3*d289c2baSAndroid Build Coastguard Worker# Copyright 2018 The Android Open Source Project
4*d289c2baSAndroid Build Coastguard Worker#
5*d289c2baSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*d289c2baSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*d289c2baSAndroid Build Coastguard Worker#
8*d289c2baSAndroid Build Coastguard Worker# You may obtain a copy of the License at
9*d289c2baSAndroid Build Coastguard Worker#
10*d289c2baSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
11*d289c2baSAndroid Build Coastguard Worker#
12*d289c2baSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
13*d289c2baSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
14*d289c2baSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15*d289c2baSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
16*d289c2baSAndroid Build Coastguard Worker# limitations under the License.
17*d289c2baSAndroid Build Coastguard Worker"""Helper tool for performing an authenticated AVB unlock of an Android Things device.
18*d289c2baSAndroid Build Coastguard Worker
19*d289c2baSAndroid Build Coastguard WorkerThis tool communicates with an Android Things device over fastboot to perform an
20*d289c2baSAndroid Build Coastguard Workerauthenticated AVB unlock. The user provides unlock credentials valid for the
21*d289c2baSAndroid Build Coastguard Workerdevice they want to unlock, likely obtained from the Android Things Developer
22*d289c2baSAndroid Build Coastguard WorkerConsole. The tool handles the sequence of fastboot commands to complete the
23*d289c2baSAndroid Build Coastguard Workerchallenge-response unlock protocol.
24*d289c2baSAndroid Build Coastguard Worker
25*d289c2baSAndroid Build Coastguard WorkerUnlock credentials can be provided to the tool in one of two ways:
26*d289c2baSAndroid Build Coastguard Worker
27*d289c2baSAndroid Build Coastguard Worker  1) by providing paths to the individual credential files using the
28*d289c2baSAndroid Build Coastguard Worker     '--pik_cert', '--puk_cert', and '--puk' command line swtiches, or
29*d289c2baSAndroid Build Coastguard Worker
30*d289c2baSAndroid Build Coastguard Worker  2) by providing a path to a zip archive containing the three credential files,
31*d289c2baSAndroid Build Coastguard Worker     named as follows:
32*d289c2baSAndroid Build Coastguard Worker       - Product Intermediate Key (PIK) certificate: 'pik_certificate.*\.bin'
33*d289c2baSAndroid Build Coastguard Worker       - Product Unlock Key (PUK) certificate: 'puk_certificate.*\.bin'
34*d289c2baSAndroid Build Coastguard Worker       - PUK private key: 'puk.*\.pem'
35*d289c2baSAndroid Build Coastguard Worker
36*d289c2baSAndroid Build Coastguard Worker     You can also provide one or more archives and/or one or more directories
37*d289c2baSAndroid Build Coastguard Worker     containing such zip archives. In either scenario, the tool will search all
38*d289c2baSAndroid Build Coastguard Worker     of the provided credential archives for a match against the product ID of
39*d289c2baSAndroid Build Coastguard Worker     the device being unlocked and automatically use the first match.
40*d289c2baSAndroid Build Coastguard Worker
41*d289c2baSAndroid Build Coastguard WorkerThis tool also clears the factory partition persistent digest unless the
42*d289c2baSAndroid Build Coastguard Worker--clear_factory_digest=false option is used. There is no harm to clear this
43*d289c2baSAndroid Build Coastguard Workerdigest even if changes to the factory partition are not planned.
44*d289c2baSAndroid Build Coastguard Worker
45*d289c2baSAndroid Build Coastguard WorkerDependencies:
46*d289c2baSAndroid Build Coastguard Worker  - Python 2.7.x, 3.2.x, or newer (for argparse)
47*d289c2baSAndroid Build Coastguard Worker  - PyCrypto 2.5 or newer (for PKCS1_v1_5 and RSA PKCS#8 PEM key import)
48*d289c2baSAndroid Build Coastguard Worker  - Android SDK Platform Tools (for fastboot), in PATH
49*d289c2baSAndroid Build Coastguard Worker    - https://developer.android.com/studio/releases/platform-tools
50*d289c2baSAndroid Build Coastguard Worker"""
51*d289c2baSAndroid Build Coastguard Worker
52*d289c2baSAndroid Build Coastguard WorkerHELP_DESCRIPTION = """Performs an authenticated AVB unlock of an Android Things device over
53*d289c2baSAndroid Build Coastguard Workerfastboot, given valid unlock credentials for the device."""
54*d289c2baSAndroid Build Coastguard Worker
55*d289c2baSAndroid Build Coastguard WorkerHELP_USAGE = """
56*d289c2baSAndroid Build Coastguard Worker  %(prog)s [-h] [-v] [-s SERIAL] [--clear_factory_digest=true|false] unlock_creds.zip [unlock_creds_2.zip ...]
57*d289c2baSAndroid Build Coastguard Worker  %(prog)s --pik_cert pik_cert.bin --puk_cert puk_cert.bin --puk puk.pem"""
58*d289c2baSAndroid Build Coastguard Worker
59*d289c2baSAndroid Build Coastguard WorkerHELP_EPILOG = """examples:
60*d289c2baSAndroid Build Coastguard Worker  %(prog)s unlock_creds.zip
61*d289c2baSAndroid Build Coastguard Worker  %(prog)s unlock_creds.zip unlock_creds_2.zip -s SERIAL
62*d289c2baSAndroid Build Coastguard Worker  %(prog)s path_to_dir_with_multiple_unlock_creds/
63*d289c2baSAndroid Build Coastguard Worker  %(prog)s --pik_cert pik_cert.bin --puk_cert puk_cert.bin --puk puk.pem"""
64*d289c2baSAndroid Build Coastguard Worker
65*d289c2baSAndroid Build Coastguard Workerimport sys
66*d289c2baSAndroid Build Coastguard Worker
67*d289c2baSAndroid Build Coastguard Workerver = sys.version_info
68*d289c2baSAndroid Build Coastguard Workerif (ver[0] < 2) or (ver[0] == 2 and ver[1] < 7) or (ver[0] == 3 and ver[1] < 2):
69*d289c2baSAndroid Build Coastguard Worker  print('This script requires Python 2.7+ or 3.2+')
70*d289c2baSAndroid Build Coastguard Worker  sys.exit(1)
71*d289c2baSAndroid Build Coastguard Worker
72*d289c2baSAndroid Build Coastguard Workerimport argparse
73*d289c2baSAndroid Build Coastguard Workerimport binascii
74*d289c2baSAndroid Build Coastguard Workerimport os
75*d289c2baSAndroid Build Coastguard Workerimport re
76*d289c2baSAndroid Build Coastguard Workerimport shutil
77*d289c2baSAndroid Build Coastguard Workerimport struct
78*d289c2baSAndroid Build Coastguard Workerimport subprocess
79*d289c2baSAndroid Build Coastguard Workerimport tempfile
80*d289c2baSAndroid Build Coastguard Workerimport zipfile
81*d289c2baSAndroid Build Coastguard Worker
82*d289c2baSAndroid Build Coastguard Worker# Requires PyCrypto 2.5 (or newer) for PKCS1_v1_5 and support for importing
83*d289c2baSAndroid Build Coastguard Worker# PEM-encoded RSA keys
84*d289c2baSAndroid Build Coastguard Workertry:
85*d289c2baSAndroid Build Coastguard Worker  from Crypto.Hash import SHA512
86*d289c2baSAndroid Build Coastguard Worker  from Crypto.PublicKey import RSA
87*d289c2baSAndroid Build Coastguard Worker  from Crypto.Signature import PKCS1_v1_5
88*d289c2baSAndroid Build Coastguard Workerexcept ImportError as e:
89*d289c2baSAndroid Build Coastguard Worker  print('PyCrypto 2.5 or newer required, missing or too old: ' + str(e))
90*d289c2baSAndroid Build Coastguard Worker
91*d289c2baSAndroid Build Coastguard Worker
92*d289c2baSAndroid Build Coastguard Workerclass UnlockCredentials(object):
93*d289c2baSAndroid Build Coastguard Worker  """Helper data container class for the 3 unlock credentials involved in an AVB authenticated unlock operation.
94*d289c2baSAndroid Build Coastguard Worker
95*d289c2baSAndroid Build Coastguard Worker  """
96*d289c2baSAndroid Build Coastguard Worker
97*d289c2baSAndroid Build Coastguard Worker  def __init__(self,
98*d289c2baSAndroid Build Coastguard Worker               intermediate_cert_file,
99*d289c2baSAndroid Build Coastguard Worker               unlock_cert_file,
100*d289c2baSAndroid Build Coastguard Worker               unlock_key_file,
101*d289c2baSAndroid Build Coastguard Worker               source_file=None):
102*d289c2baSAndroid Build Coastguard Worker    # The certificates are AvbCertCertificate structs as defined in libavb_cert,
103*d289c2baSAndroid Build Coastguard Worker    # not an X.509 certificate. Do a basic length sanity check when reading
104*d289c2baSAndroid Build Coastguard Worker    # them.
105*d289c2baSAndroid Build Coastguard Worker    EXPECTED_CERTIFICATE_SIZE = 1620
106*d289c2baSAndroid Build Coastguard Worker
107*d289c2baSAndroid Build Coastguard Worker    with open(intermediate_cert_file, 'rb') as f:
108*d289c2baSAndroid Build Coastguard Worker      self._intermediate_cert = f.read()
109*d289c2baSAndroid Build Coastguard Worker    if len(self._intermediate_cert) != EXPECTED_CERTIFICATE_SIZE:
110*d289c2baSAndroid Build Coastguard Worker      raise ValueError('Invalid intermediate key certificate length.')
111*d289c2baSAndroid Build Coastguard Worker
112*d289c2baSAndroid Build Coastguard Worker    with open(unlock_cert_file, 'rb') as f:
113*d289c2baSAndroid Build Coastguard Worker      self._unlock_cert = f.read()
114*d289c2baSAndroid Build Coastguard Worker    if len(self._unlock_cert) != EXPECTED_CERTIFICATE_SIZE:
115*d289c2baSAndroid Build Coastguard Worker      raise ValueError('Invalid product unlock key certificate length.')
116*d289c2baSAndroid Build Coastguard Worker
117*d289c2baSAndroid Build Coastguard Worker    with open(unlock_key_file, 'rb') as f:
118*d289c2baSAndroid Build Coastguard Worker      self._unlock_key = RSA.importKey(f.read())
119*d289c2baSAndroid Build Coastguard Worker      if not self._unlock_key.has_private():
120*d289c2baSAndroid Build Coastguard Worker        raise ValueError('Unlock key was not an RSA private key.')
121*d289c2baSAndroid Build Coastguard Worker
122*d289c2baSAndroid Build Coastguard Worker    self._source_file = source_file
123*d289c2baSAndroid Build Coastguard Worker
124*d289c2baSAndroid Build Coastguard Worker  @property
125*d289c2baSAndroid Build Coastguard Worker  def intermediate_cert(self):
126*d289c2baSAndroid Build Coastguard Worker    return self._intermediate_cert
127*d289c2baSAndroid Build Coastguard Worker
128*d289c2baSAndroid Build Coastguard Worker  @property
129*d289c2baSAndroid Build Coastguard Worker  def unlock_cert(self):
130*d289c2baSAndroid Build Coastguard Worker    return self._unlock_cert
131*d289c2baSAndroid Build Coastguard Worker
132*d289c2baSAndroid Build Coastguard Worker  @property
133*d289c2baSAndroid Build Coastguard Worker  def unlock_key(self):
134*d289c2baSAndroid Build Coastguard Worker    return self._unlock_key
135*d289c2baSAndroid Build Coastguard Worker
136*d289c2baSAndroid Build Coastguard Worker  @property
137*d289c2baSAndroid Build Coastguard Worker  def source_file(self):
138*d289c2baSAndroid Build Coastguard Worker    return self._source_file
139*d289c2baSAndroid Build Coastguard Worker
140*d289c2baSAndroid Build Coastguard Worker  @classmethod
141*d289c2baSAndroid Build Coastguard Worker  def from_credential_archive(cls, archive):
142*d289c2baSAndroid Build Coastguard Worker    """Create UnlockCredentials from an unlock credential zip archive.
143*d289c2baSAndroid Build Coastguard Worker
144*d289c2baSAndroid Build Coastguard Worker    The zip archive must contain the following three credential files, named as
145*d289c2baSAndroid Build Coastguard Worker    follows:
146*d289c2baSAndroid Build Coastguard Worker      - Product Intermediate Key (PIK) certificate: 'pik_certificate.*\.bin'
147*d289c2baSAndroid Build Coastguard Worker      - Product Unlock Key (PUK) certificate: 'puk_certificate.*\.bin'
148*d289c2baSAndroid Build Coastguard Worker      - PUK private key: 'puk.*\.pem'
149*d289c2baSAndroid Build Coastguard Worker
150*d289c2baSAndroid Build Coastguard Worker    This uses @contextlib.contextmanager so we can clean up the tempdir created
151*d289c2baSAndroid Build Coastguard Worker    to unpack the zip contents into.
152*d289c2baSAndroid Build Coastguard Worker
153*d289c2baSAndroid Build Coastguard Worker    Arguments:
154*d289c2baSAndroid Build Coastguard Worker      - archive: Filename of zip archive containing unlock credentials.
155*d289c2baSAndroid Build Coastguard Worker
156*d289c2baSAndroid Build Coastguard Worker    Raises:
157*d289c2baSAndroid Build Coastguard Worker      ValueError: If archive is either missing a required file or contains
158*d289c2baSAndroid Build Coastguard Worker      multiple files matching one of the filename formats.
159*d289c2baSAndroid Build Coastguard Worker    """
160*d289c2baSAndroid Build Coastguard Worker
161*d289c2baSAndroid Build Coastguard Worker    def _find_one_match(contents, regex, desc):
162*d289c2baSAndroid Build Coastguard Worker      r = re.compile(regex)
163*d289c2baSAndroid Build Coastguard Worker      matches = list(filter(r.search, contents))
164*d289c2baSAndroid Build Coastguard Worker      if not matches:
165*d289c2baSAndroid Build Coastguard Worker        raise ValueError(
166*d289c2baSAndroid Build Coastguard Worker            "Couldn't find {} file (matching regex '{}') in archive {}".format(
167*d289c2baSAndroid Build Coastguard Worker                desc, regex, archive))
168*d289c2baSAndroid Build Coastguard Worker      elif len(matches) > 1:
169*d289c2baSAndroid Build Coastguard Worker        raise ValueError(
170*d289c2baSAndroid Build Coastguard Worker            "Found multiple files for {} (matching regex '{}') in archive {}"
171*d289c2baSAndroid Build Coastguard Worker            .format(desc, regex, archive))
172*d289c2baSAndroid Build Coastguard Worker      return matches[0]
173*d289c2baSAndroid Build Coastguard Worker
174*d289c2baSAndroid Build Coastguard Worker    tempdir = tempfile.mkdtemp()
175*d289c2baSAndroid Build Coastguard Worker    try:
176*d289c2baSAndroid Build Coastguard Worker      with zipfile.ZipFile(archive, mode='r') as zip:
177*d289c2baSAndroid Build Coastguard Worker        contents = zip.namelist()
178*d289c2baSAndroid Build Coastguard Worker
179*d289c2baSAndroid Build Coastguard Worker        pik_cert_re = r'^pik_certificate.*\.bin$'
180*d289c2baSAndroid Build Coastguard Worker        pik_cert = _find_one_match(contents, pik_cert_re,
181*d289c2baSAndroid Build Coastguard Worker                                   'intermediate key (PIK) certificate')
182*d289c2baSAndroid Build Coastguard Worker
183*d289c2baSAndroid Build Coastguard Worker        puk_cert_re = r'^puk_certificate.*\.bin$'
184*d289c2baSAndroid Build Coastguard Worker        puk_cert = _find_one_match(contents, puk_cert_re,
185*d289c2baSAndroid Build Coastguard Worker                                   'unlock key (PUK) certificate')
186*d289c2baSAndroid Build Coastguard Worker
187*d289c2baSAndroid Build Coastguard Worker        puk_re = r'^puk.*\.pem$'
188*d289c2baSAndroid Build Coastguard Worker        puk = _find_one_match(contents, puk_re, 'unlock key (PUK)')
189*d289c2baSAndroid Build Coastguard Worker
190*d289c2baSAndroid Build Coastguard Worker        zip.extractall(path=tempdir, members=[pik_cert, puk_cert, puk])
191*d289c2baSAndroid Build Coastguard Worker
192*d289c2baSAndroid Build Coastguard Worker        return cls(
193*d289c2baSAndroid Build Coastguard Worker            intermediate_cert_file=os.path.join(tempdir, pik_cert),
194*d289c2baSAndroid Build Coastguard Worker            unlock_cert_file=os.path.join(tempdir, puk_cert),
195*d289c2baSAndroid Build Coastguard Worker            unlock_key_file=os.path.join(tempdir, puk),
196*d289c2baSAndroid Build Coastguard Worker            source_file=archive)
197*d289c2baSAndroid Build Coastguard Worker    finally:
198*d289c2baSAndroid Build Coastguard Worker      shutil.rmtree(tempdir)
199*d289c2baSAndroid Build Coastguard Worker
200*d289c2baSAndroid Build Coastguard Worker
201*d289c2baSAndroid Build Coastguard Workerclass UnlockChallenge(object):
202*d289c2baSAndroid Build Coastguard Worker  """Helper class for parsing the AvbCertUnlockChallenge struct returned from 'fastboot oem at-get-vboot-unlock-challenge'.
203*d289c2baSAndroid Build Coastguard Worker
204*d289c2baSAndroid Build Coastguard Worker     The file provided to the constructor should be the full 52-byte
205*d289c2baSAndroid Build Coastguard Worker     AvbCertUnlockChallenge struct, not just the challenge itself.
206*d289c2baSAndroid Build Coastguard Worker  """
207*d289c2baSAndroid Build Coastguard Worker
208*d289c2baSAndroid Build Coastguard Worker  def __init__(self, challenge_file):
209*d289c2baSAndroid Build Coastguard Worker    CHALLENGE_STRUCT_SIZE = 52
210*d289c2baSAndroid Build Coastguard Worker    PRODUCT_ID_HASH_SIZE = 32
211*d289c2baSAndroid Build Coastguard Worker    CHALLENGE_DATA_SIZE = 16
212*d289c2baSAndroid Build Coastguard Worker    with open(challenge_file, 'rb') as f:
213*d289c2baSAndroid Build Coastguard Worker      data = f.read()
214*d289c2baSAndroid Build Coastguard Worker      if len(data) != CHALLENGE_STRUCT_SIZE:
215*d289c2baSAndroid Build Coastguard Worker        raise ValueError('Invalid unlock challenge length.')
216*d289c2baSAndroid Build Coastguard Worker
217*d289c2baSAndroid Build Coastguard Worker      self._version, self._product_id_hash, self._challenge_data = struct.unpack(
218*d289c2baSAndroid Build Coastguard Worker          '<I{}s{}s'.format(PRODUCT_ID_HASH_SIZE, CHALLENGE_DATA_SIZE), data)
219*d289c2baSAndroid Build Coastguard Worker
220*d289c2baSAndroid Build Coastguard Worker  @property
221*d289c2baSAndroid Build Coastguard Worker  def version(self):
222*d289c2baSAndroid Build Coastguard Worker    return self._version
223*d289c2baSAndroid Build Coastguard Worker
224*d289c2baSAndroid Build Coastguard Worker  @property
225*d289c2baSAndroid Build Coastguard Worker  def product_id_hash(self):
226*d289c2baSAndroid Build Coastguard Worker    return self._product_id_hash
227*d289c2baSAndroid Build Coastguard Worker
228*d289c2baSAndroid Build Coastguard Worker  @property
229*d289c2baSAndroid Build Coastguard Worker  def challenge_data(self):
230*d289c2baSAndroid Build Coastguard Worker    return self._challenge_data
231*d289c2baSAndroid Build Coastguard Worker
232*d289c2baSAndroid Build Coastguard Worker
233*d289c2baSAndroid Build Coastguard Workerdef GetCertCertificateSubject(cert):
234*d289c2baSAndroid Build Coastguard Worker  """Parses and returns the subject field from the given AvbCertCertificate struct."""
235*d289c2baSAndroid Build Coastguard Worker  CERT_SUBJECT_OFFSET = 4 + 1032  # Format version and public key come before subject
236*d289c2baSAndroid Build Coastguard Worker  CERT_SUBJECT_LENGTH = 32
237*d289c2baSAndroid Build Coastguard Worker  return cert[CERT_SUBJECT_OFFSET:CERT_SUBJECT_OFFSET + CERT_SUBJECT_LENGTH]
238*d289c2baSAndroid Build Coastguard Worker
239*d289c2baSAndroid Build Coastguard Worker
240*d289c2baSAndroid Build Coastguard Workerdef SelectMatchingUnlockCredential(all_creds, challenge):
241*d289c2baSAndroid Build Coastguard Worker  """Find and return the first UnlockCredentials object whose product ID matches that of the unlock challenge.
242*d289c2baSAndroid Build Coastguard Worker
243*d289c2baSAndroid Build Coastguard Worker  The Product Unlock Key (PUK) certificate's subject field contains the
244*d289c2baSAndroid Build Coastguard Worker  SHA256 hash of the product ID that it can be used to unlock. This same
245*d289c2baSAndroid Build Coastguard Worker  value (SHA256 hash of the product ID) is contained in the unlock challenge.
246*d289c2baSAndroid Build Coastguard Worker
247*d289c2baSAndroid Build Coastguard Worker  Arguments:
248*d289c2baSAndroid Build Coastguard Worker    all_creds: List of UnlockCredentials objects to be searched for a match
249*d289c2baSAndroid Build Coastguard Worker      against the given challenge.
250*d289c2baSAndroid Build Coastguard Worker    challenge: UnlockChallenge object created from challenge obtained via
251*d289c2baSAndroid Build Coastguard Worker      'fastboot oem at-get-vboot-unlock-challenge'.
252*d289c2baSAndroid Build Coastguard Worker  """
253*d289c2baSAndroid Build Coastguard Worker  for creds in all_creds:
254*d289c2baSAndroid Build Coastguard Worker    if GetCertCertificateSubject(creds.unlock_cert) == challenge.product_id_hash:
255*d289c2baSAndroid Build Coastguard Worker      return creds
256*d289c2baSAndroid Build Coastguard Worker
257*d289c2baSAndroid Build Coastguard Worker
258*d289c2baSAndroid Build Coastguard Workerdef MakeCertUnlockCredential(creds, challenge, out_file):
259*d289c2baSAndroid Build Coastguard Worker  """Simple reimplementation of 'avbtool make_cert_unlock_credential'.
260*d289c2baSAndroid Build Coastguard Worker
261*d289c2baSAndroid Build Coastguard Worker  Generates an Android Things authenticated unlock credential to authorize
262*d289c2baSAndroid Build Coastguard Worker  unlocking AVB on a device.
263*d289c2baSAndroid Build Coastguard Worker
264*d289c2baSAndroid Build Coastguard Worker  This is reimplemented locally for simplicity, which avoids the need to bundle
265*d289c2baSAndroid Build Coastguard Worker  this tool with the full avbtool. avbtool also uses openssl by default whereas
266*d289c2baSAndroid Build Coastguard Worker  this uses PyCrypto, which makes it easier to support Windows since there are
267*d289c2baSAndroid Build Coastguard Worker  no officially supported openssl binary distributions.
268*d289c2baSAndroid Build Coastguard Worker
269*d289c2baSAndroid Build Coastguard Worker  Arguments:
270*d289c2baSAndroid Build Coastguard Worker    creds: UnlockCredentials object wrapping the PIK certificate, PUK
271*d289c2baSAndroid Build Coastguard Worker      certificate, and PUK private key.
272*d289c2baSAndroid Build Coastguard Worker    challenge: UnlockChallenge object created from challenge obtained via
273*d289c2baSAndroid Build Coastguard Worker      'fastboot oem at-get-vboot-unlock-challenge'.
274*d289c2baSAndroid Build Coastguard Worker    out_file: Output filename to write the AvbCertUnlockCredential struct to.
275*d289c2baSAndroid Build Coastguard Worker
276*d289c2baSAndroid Build Coastguard Worker  Raises:
277*d289c2baSAndroid Build Coastguard Worker    ValueError: If challenge has wrong length.
278*d289c2baSAndroid Build Coastguard Worker  """
279*d289c2baSAndroid Build Coastguard Worker  hash = SHA512.new(challenge.challenge_data)
280*d289c2baSAndroid Build Coastguard Worker  signer = PKCS1_v1_5.new(creds.unlock_key)
281*d289c2baSAndroid Build Coastguard Worker  signature = signer.sign(hash)
282*d289c2baSAndroid Build Coastguard Worker
283*d289c2baSAndroid Build Coastguard Worker  with open(out_file, 'wb') as out:
284*d289c2baSAndroid Build Coastguard Worker    out.write(struct.pack('<I', 1))  # Format Version
285*d289c2baSAndroid Build Coastguard Worker    out.write(creds.intermediate_cert)
286*d289c2baSAndroid Build Coastguard Worker    out.write(creds.unlock_cert)
287*d289c2baSAndroid Build Coastguard Worker    out.write(signature)
288*d289c2baSAndroid Build Coastguard Worker
289*d289c2baSAndroid Build Coastguard Worker
290*d289c2baSAndroid Build Coastguard Workerdef AuthenticatedUnlock(all_creds, serial=None, verbose=False):
291*d289c2baSAndroid Build Coastguard Worker  """Performs an authenticated AVB unlock of a device over fastboot.
292*d289c2baSAndroid Build Coastguard Worker
293*d289c2baSAndroid Build Coastguard Worker  Arguments:
294*d289c2baSAndroid Build Coastguard Worker    all_creds: List of UnlockCredentials objects wrapping the PIK certificate,
295*d289c2baSAndroid Build Coastguard Worker      PUK certificate, and PUK private key. The list will be searched to find
296*d289c2baSAndroid Build Coastguard Worker      matching credentials for the device being unlocked.
297*d289c2baSAndroid Build Coastguard Worker    serial: [optional] A device serial number or other valid value to be passed
298*d289c2baSAndroid Build Coastguard Worker      to fastboot's '-s' switch to select the device to unlock.
299*d289c2baSAndroid Build Coastguard Worker    verbose: [optional] Enable verbose output, which prints the fastboot
300*d289c2baSAndroid Build Coastguard Worker      commands and their output as the commands are run.
301*d289c2baSAndroid Build Coastguard Worker  """
302*d289c2baSAndroid Build Coastguard Worker
303*d289c2baSAndroid Build Coastguard Worker  tempdir = tempfile.mkdtemp()
304*d289c2baSAndroid Build Coastguard Worker  try:
305*d289c2baSAndroid Build Coastguard Worker    challenge_file = os.path.join(tempdir, 'challenge')
306*d289c2baSAndroid Build Coastguard Worker    credential_file = os.path.join(tempdir, 'credential')
307*d289c2baSAndroid Build Coastguard Worker
308*d289c2baSAndroid Build Coastguard Worker    def fastboot_cmd(args):
309*d289c2baSAndroid Build Coastguard Worker      args = ['fastboot'] + (['-s', serial] if serial else []) + args
310*d289c2baSAndroid Build Coastguard Worker      if verbose:
311*d289c2baSAndroid Build Coastguard Worker        print('\n$ ' + ' '.join(args))
312*d289c2baSAndroid Build Coastguard Worker
313*d289c2baSAndroid Build Coastguard Worker      out = subprocess.check_output(
314*d289c2baSAndroid Build Coastguard Worker          args, stderr=subprocess.STDOUT).decode('utf-8')
315*d289c2baSAndroid Build Coastguard Worker
316*d289c2baSAndroid Build Coastguard Worker      if verbose:
317*d289c2baSAndroid Build Coastguard Worker        print(out)
318*d289c2baSAndroid Build Coastguard Worker      return out
319*d289c2baSAndroid Build Coastguard Worker
320*d289c2baSAndroid Build Coastguard Worker    try:
321*d289c2baSAndroid Build Coastguard Worker      fastboot_cmd(['oem', 'at-get-vboot-unlock-challenge'])
322*d289c2baSAndroid Build Coastguard Worker      fastboot_cmd(['get_staged', challenge_file])
323*d289c2baSAndroid Build Coastguard Worker
324*d289c2baSAndroid Build Coastguard Worker      challenge = UnlockChallenge(challenge_file)
325*d289c2baSAndroid Build Coastguard Worker      print('Product ID SHA256 hash = {}'.format(
326*d289c2baSAndroid Build Coastguard Worker          binascii.hexlify(challenge.product_id_hash)))
327*d289c2baSAndroid Build Coastguard Worker
328*d289c2baSAndroid Build Coastguard Worker      selected_cred = SelectMatchingUnlockCredential(all_creds, challenge)
329*d289c2baSAndroid Build Coastguard Worker      if not selected_cred:
330*d289c2baSAndroid Build Coastguard Worker        print(
331*d289c2baSAndroid Build Coastguard Worker            'ERROR: None of the provided unlock credentials match this device.')
332*d289c2baSAndroid Build Coastguard Worker        return False
333*d289c2baSAndroid Build Coastguard Worker      if selected_cred.source_file:
334*d289c2baSAndroid Build Coastguard Worker        print('Found matching unlock credentials: {}'.format(
335*d289c2baSAndroid Build Coastguard Worker            selected_cred.source_file))
336*d289c2baSAndroid Build Coastguard Worker      MakeCertUnlockCredential(selected_cred, challenge, credential_file)
337*d289c2baSAndroid Build Coastguard Worker
338*d289c2baSAndroid Build Coastguard Worker      fastboot_cmd(['stage', credential_file])
339*d289c2baSAndroid Build Coastguard Worker      fastboot_cmd(['oem', 'at-unlock-vboot'])
340*d289c2baSAndroid Build Coastguard Worker
341*d289c2baSAndroid Build Coastguard Worker      res = fastboot_cmd(['getvar', 'at-vboot-state'])
342*d289c2baSAndroid Build Coastguard Worker      if re.search(r'avb-locked(:\s*|=)0', res) is not None:
343*d289c2baSAndroid Build Coastguard Worker        print('Device successfully AVB unlocked')
344*d289c2baSAndroid Build Coastguard Worker        return True
345*d289c2baSAndroid Build Coastguard Worker      else:
346*d289c2baSAndroid Build Coastguard Worker        print('ERROR: Commands succeeded but device still locked')
347*d289c2baSAndroid Build Coastguard Worker        return False
348*d289c2baSAndroid Build Coastguard Worker    except subprocess.CalledProcessError as e:
349*d289c2baSAndroid Build Coastguard Worker      print(e.output.decode('utf-8'))
350*d289c2baSAndroid Build Coastguard Worker      print("Command '{}' returned non-zero exit status {}".format(
351*d289c2baSAndroid Build Coastguard Worker          ' '.join(e.cmd), e.returncode))
352*d289c2baSAndroid Build Coastguard Worker      return False
353*d289c2baSAndroid Build Coastguard Worker  finally:
354*d289c2baSAndroid Build Coastguard Worker    shutil.rmtree(tempdir)
355*d289c2baSAndroid Build Coastguard Worker
356*d289c2baSAndroid Build Coastguard Worker
357*d289c2baSAndroid Build Coastguard Workerdef FindUnlockCredentialsInDirectory(dir, verbose=False):
358*d289c2baSAndroid Build Coastguard Worker  if not os.path.isdir(dir):
359*d289c2baSAndroid Build Coastguard Worker    raise ValueError('Not a directory: ' + dir)
360*d289c2baSAndroid Build Coastguard Worker
361*d289c2baSAndroid Build Coastguard Worker  creds = []
362*d289c2baSAndroid Build Coastguard Worker  for file in os.listdir(dir):
363*d289c2baSAndroid Build Coastguard Worker    path = os.path.join(dir, file)
364*d289c2baSAndroid Build Coastguard Worker    if os.path.isfile(path):
365*d289c2baSAndroid Build Coastguard Worker      try:
366*d289c2baSAndroid Build Coastguard Worker        creds.append(UnlockCredentials.from_credential_archive(path))
367*d289c2baSAndroid Build Coastguard Worker        if verbose:
368*d289c2baSAndroid Build Coastguard Worker          print('Found valid unlock credential bundle: ' + path)
369*d289c2baSAndroid Build Coastguard Worker      except (IOError, ValueError, zipfile.BadZipfile) as e:
370*d289c2baSAndroid Build Coastguard Worker        if verbose:
371*d289c2baSAndroid Build Coastguard Worker          print(
372*d289c2baSAndroid Build Coastguard Worker              "Ignoring file which isn't a valid unlock credential zip bundle: "
373*d289c2baSAndroid Build Coastguard Worker              + path)
374*d289c2baSAndroid Build Coastguard Worker  return creds
375*d289c2baSAndroid Build Coastguard Worker
376*d289c2baSAndroid Build Coastguard Worker
377*d289c2baSAndroid Build Coastguard Workerdef ClearFactoryPersistentDigest(serial=None, verbose=False):
378*d289c2baSAndroid Build Coastguard Worker  """Clears the factory partition persistent digest using fastboot.
379*d289c2baSAndroid Build Coastguard Worker
380*d289c2baSAndroid Build Coastguard Worker  Most of the time this should be cleared when unlocking a device because
381*d289c2baSAndroid Build Coastguard Worker  otherwise any attempts to update the factory partition will be rejected once
382*d289c2baSAndroid Build Coastguard Worker  the device is again locked, causing confusion. There is no harm to clear this
383*d289c2baSAndroid Build Coastguard Worker  digest even if factory partition updates are not planned.
384*d289c2baSAndroid Build Coastguard Worker
385*d289c2baSAndroid Build Coastguard Worker  Arguments:
386*d289c2baSAndroid Build Coastguard Worker    serial: [optional] A device serial number or other valid value to be passed
387*d289c2baSAndroid Build Coastguard Worker      to fastboot's '-s' switch to select the device to unlock.
388*d289c2baSAndroid Build Coastguard Worker    verbose: [optional] Enable verbose output, which prints the fastboot
389*d289c2baSAndroid Build Coastguard Worker      commands and their output as the commands are run.
390*d289c2baSAndroid Build Coastguard Worker  """
391*d289c2baSAndroid Build Coastguard Worker  FACTORY_PERSISTENT_DIGEST_NAME = 'avb.persistent_digest.factory'
392*d289c2baSAndroid Build Coastguard Worker
393*d289c2baSAndroid Build Coastguard Worker  tempdir = tempfile.mkdtemp()
394*d289c2baSAndroid Build Coastguard Worker  try:
395*d289c2baSAndroid Build Coastguard Worker    digest_data = os.path.join(tempdir, 'digest_data')
396*d289c2baSAndroid Build Coastguard Worker
397*d289c2baSAndroid Build Coastguard Worker    with open(digest_data, 'wb') as out:
398*d289c2baSAndroid Build Coastguard Worker      out.write(struct.pack('<I', len(FACTORY_PERSISTENT_DIGEST_NAME)))
399*d289c2baSAndroid Build Coastguard Worker      out.write(FACTORY_PERSISTENT_DIGEST_NAME)
400*d289c2baSAndroid Build Coastguard Worker      # Sending a zero length digest will clear the existing digest.
401*d289c2baSAndroid Build Coastguard Worker      out.write(struct.pack('<I', 0))
402*d289c2baSAndroid Build Coastguard Worker
403*d289c2baSAndroid Build Coastguard Worker    def fastboot_cmd(args):
404*d289c2baSAndroid Build Coastguard Worker      args = ['fastboot'] + (['-s', serial] if serial else []) + args
405*d289c2baSAndroid Build Coastguard Worker      if verbose:
406*d289c2baSAndroid Build Coastguard Worker        print('$ ' + ' '.join(args))
407*d289c2baSAndroid Build Coastguard Worker
408*d289c2baSAndroid Build Coastguard Worker      out = subprocess.check_output(
409*d289c2baSAndroid Build Coastguard Worker          args, stderr=subprocess.STDOUT).decode('utf-8')
410*d289c2baSAndroid Build Coastguard Worker
411*d289c2baSAndroid Build Coastguard Worker      if verbose:
412*d289c2baSAndroid Build Coastguard Worker        print(out)
413*d289c2baSAndroid Build Coastguard Worker
414*d289c2baSAndroid Build Coastguard Worker    try:
415*d289c2baSAndroid Build Coastguard Worker      fastboot_cmd(['stage', digest_data])
416*d289c2baSAndroid Build Coastguard Worker      fastboot_cmd(['oem', 'at-write-persistent-digest'])
417*d289c2baSAndroid Build Coastguard Worker      print("Successfully cleared the factory partition persistent digest.")
418*d289c2baSAndroid Build Coastguard Worker      return True
419*d289c2baSAndroid Build Coastguard Worker    except subprocess.CalledProcessError as e:
420*d289c2baSAndroid Build Coastguard Worker      print(e.output.decode('utf-8'))
421*d289c2baSAndroid Build Coastguard Worker      print("Command '{}' returned non-zero exit status {}".format(
422*d289c2baSAndroid Build Coastguard Worker          ' '.join(e.cmd), e.returncode))
423*d289c2baSAndroid Build Coastguard Worker      print("Warning: Failed to clear factory partition persistent digest.")
424*d289c2baSAndroid Build Coastguard Worker      return False
425*d289c2baSAndroid Build Coastguard Worker
426*d289c2baSAndroid Build Coastguard Worker  finally:
427*d289c2baSAndroid Build Coastguard Worker    shutil.rmtree(tempdir)
428*d289c2baSAndroid Build Coastguard Worker
429*d289c2baSAndroid Build Coastguard Worker
430*d289c2baSAndroid Build Coastguard Workerdef parse_boolean(value):
431*d289c2baSAndroid Build Coastguard Worker  if value.strip().lower() in ('true', 't', 'yes', 'y', 'on', '1'):
432*d289c2baSAndroid Build Coastguard Worker      return True
433*d289c2baSAndroid Build Coastguard Worker  elif value.strip().lower() in ('false', 'f', 'no', 'n', 'off', '0'):
434*d289c2baSAndroid Build Coastguard Worker      return False
435*d289c2baSAndroid Build Coastguard Worker  else:
436*d289c2baSAndroid Build Coastguard Worker      raise argparse.ArgumentTypeError('Unexpected boolean value: %s' % value)
437*d289c2baSAndroid Build Coastguard Worker
438*d289c2baSAndroid Build Coastguard Workerdef main(in_args):
439*d289c2baSAndroid Build Coastguard Worker  parser = argparse.ArgumentParser(
440*d289c2baSAndroid Build Coastguard Worker      description=HELP_DESCRIPTION,
441*d289c2baSAndroid Build Coastguard Worker      usage=HELP_USAGE,
442*d289c2baSAndroid Build Coastguard Worker      epilog=HELP_EPILOG,
443*d289c2baSAndroid Build Coastguard Worker      formatter_class=argparse.RawDescriptionHelpFormatter)
444*d289c2baSAndroid Build Coastguard Worker
445*d289c2baSAndroid Build Coastguard Worker  # General optional arguments.
446*d289c2baSAndroid Build Coastguard Worker  parser.add_argument(
447*d289c2baSAndroid Build Coastguard Worker      '-v',
448*d289c2baSAndroid Build Coastguard Worker      '--verbose',
449*d289c2baSAndroid Build Coastguard Worker      action='store_true',
450*d289c2baSAndroid Build Coastguard Worker      help=
451*d289c2baSAndroid Build Coastguard Worker      'enable verbose output, e.g. prints fastboot commands and their output')
452*d289c2baSAndroid Build Coastguard Worker  parser.add_argument(
453*d289c2baSAndroid Build Coastguard Worker      '-s',
454*d289c2baSAndroid Build Coastguard Worker      '--serial',
455*d289c2baSAndroid Build Coastguard Worker      help=
456*d289c2baSAndroid Build Coastguard Worker      "specify device to unlock, either by serial or any other valid value for fastboot's -s arg"
457*d289c2baSAndroid Build Coastguard Worker  )
458*d289c2baSAndroid Build Coastguard Worker  parser.add_argument(
459*d289c2baSAndroid Build Coastguard Worker      '--clear_factory_digest',
460*d289c2baSAndroid Build Coastguard Worker      nargs='?',
461*d289c2baSAndroid Build Coastguard Worker      type=parse_boolean,
462*d289c2baSAndroid Build Coastguard Worker      default='true',
463*d289c2baSAndroid Build Coastguard Worker      const='true',
464*d289c2baSAndroid Build Coastguard Worker      help='Defaults to true. Set to false to prevent clearing the factory persistent digest')
465*d289c2baSAndroid Build Coastguard Worker
466*d289c2baSAndroid Build Coastguard Worker  # User must provide either a unlock credential bundle, or the individual files
467*d289c2baSAndroid Build Coastguard Worker  # normally contained in such a bundle.
468*d289c2baSAndroid Build Coastguard Worker  # argparse doesn't support specifying this argument format - two groups of
469*d289c2baSAndroid Build Coastguard Worker  # mutually exclusive arguments, where one group requires all arguments in that
470*d289c2baSAndroid Build Coastguard Worker  # group to be specified - so we define them as optional arguments and do the
471*d289c2baSAndroid Build Coastguard Worker  # validation ourselves below.
472*d289c2baSAndroid Build Coastguard Worker
473*d289c2baSAndroid Build Coastguard Worker  # Argument group #1 - Unlock credential zip archive(s) (or directory
474*d289c2baSAndroid Build Coastguard Worker  # containing multiple such archives)
475*d289c2baSAndroid Build Coastguard Worker  parser.add_argument(
476*d289c2baSAndroid Build Coastguard Worker      'bundle',
477*d289c2baSAndroid Build Coastguard Worker      metavar='unlock_creds.zip',
478*d289c2baSAndroid Build Coastguard Worker      nargs='*',
479*d289c2baSAndroid Build Coastguard Worker      help=
480*d289c2baSAndroid Build Coastguard Worker      'Unlock using a zip bundle/archive of credentials (e.g. from Developer '
481*d289c2baSAndroid Build Coastguard Worker      'Console). You can optionally provide multiple archives and/or a  '
482*d289c2baSAndroid Build Coastguard Worker      'directory of such bundles and the tool will automatically select the '
483*d289c2baSAndroid Build Coastguard Worker      'correct one to use based on matching the product ID against the device '
484*d289c2baSAndroid Build Coastguard Worker      'being unlocked.')
485*d289c2baSAndroid Build Coastguard Worker
486*d289c2baSAndroid Build Coastguard Worker  # Argument group #2 - Individual credential files
487*d289c2baSAndroid Build Coastguard Worker  parser.add_argument(
488*d289c2baSAndroid Build Coastguard Worker      '--pik_cert',
489*d289c2baSAndroid Build Coastguard Worker      metavar='pik_cert.bin',
490*d289c2baSAndroid Build Coastguard Worker      help='Path to product intermediate key (PIK) certificate file')
491*d289c2baSAndroid Build Coastguard Worker  parser.add_argument(
492*d289c2baSAndroid Build Coastguard Worker      '--puk_cert',
493*d289c2baSAndroid Build Coastguard Worker      metavar='puk_cert.bin',
494*d289c2baSAndroid Build Coastguard Worker      help='Path to product unlock key (PUK) certificate file')
495*d289c2baSAndroid Build Coastguard Worker  parser.add_argument(
496*d289c2baSAndroid Build Coastguard Worker      '--puk',
497*d289c2baSAndroid Build Coastguard Worker      metavar='puk.pem',
498*d289c2baSAndroid Build Coastguard Worker      help='Path to product unlock key in PEM format')
499*d289c2baSAndroid Build Coastguard Worker
500*d289c2baSAndroid Build Coastguard Worker  # Print help if no args given
501*d289c2baSAndroid Build Coastguard Worker  args = parser.parse_args(in_args if in_args else ['-h'])
502*d289c2baSAndroid Build Coastguard Worker
503*d289c2baSAndroid Build Coastguard Worker  # Do the custom validation described above.
504*d289c2baSAndroid Build Coastguard Worker  if args.pik_cert is not None or args.puk_cert is not None or args.puk is not None:
505*d289c2baSAndroid Build Coastguard Worker    # Check mutual exclusion with bundle positional argument
506*d289c2baSAndroid Build Coastguard Worker    if len(args.bundle):
507*d289c2baSAndroid Build Coastguard Worker      parser.error(
508*d289c2baSAndroid Build Coastguard Worker          'bundle argument is mutually exclusive with --pik_cert, --puk_cert, and --puk'
509*d289c2baSAndroid Build Coastguard Worker      )
510*d289c2baSAndroid Build Coastguard Worker
511*d289c2baSAndroid Build Coastguard Worker    # Check for 'mutual inclusion' of individual file options
512*d289c2baSAndroid Build Coastguard Worker    if args.pik_cert is None:
513*d289c2baSAndroid Build Coastguard Worker      parser.error("--pik_cert is required if --puk_cert or --puk' is given")
514*d289c2baSAndroid Build Coastguard Worker    if args.puk_cert is None:
515*d289c2baSAndroid Build Coastguard Worker      parser.error("--puk_cert is required if --pik_cert or --puk' is given")
516*d289c2baSAndroid Build Coastguard Worker    if args.puk is None:
517*d289c2baSAndroid Build Coastguard Worker      parser.error("--puk is required if --pik_cert or --puk_cert' is given")
518*d289c2baSAndroid Build Coastguard Worker  elif not len(args.bundle):
519*d289c2baSAndroid Build Coastguard Worker    parser.error(
520*d289c2baSAndroid Build Coastguard Worker        'must provide either credentials bundle or individual credential files')
521*d289c2baSAndroid Build Coastguard Worker
522*d289c2baSAndroid Build Coastguard Worker  # Parse arguments into UnlockCredentials objects
523*d289c2baSAndroid Build Coastguard Worker  if len(args.bundle):
524*d289c2baSAndroid Build Coastguard Worker    creds = []
525*d289c2baSAndroid Build Coastguard Worker    for path in args.bundle:
526*d289c2baSAndroid Build Coastguard Worker      if os.path.isfile(path):
527*d289c2baSAndroid Build Coastguard Worker        creds.append(UnlockCredentials.from_credential_archive(path))
528*d289c2baSAndroid Build Coastguard Worker      elif os.path.isdir(path):
529*d289c2baSAndroid Build Coastguard Worker        creds.extend(
530*d289c2baSAndroid Build Coastguard Worker            FindUnlockCredentialsInDirectory(path, verbose=args.verbose))
531*d289c2baSAndroid Build Coastguard Worker      else:
532*d289c2baSAndroid Build Coastguard Worker        parser.error("path argument '{}' does not exist".format(path))
533*d289c2baSAndroid Build Coastguard Worker
534*d289c2baSAndroid Build Coastguard Worker    if len(creds) == 0:
535*d289c2baSAndroid Build Coastguard Worker      parser.error('No unlock credentials were found in any of the given paths')
536*d289c2baSAndroid Build Coastguard Worker  else:
537*d289c2baSAndroid Build Coastguard Worker    creds = [UnlockCredentials(args.pik_cert, args.puk_cert, args.puk)]
538*d289c2baSAndroid Build Coastguard Worker
539*d289c2baSAndroid Build Coastguard Worker  ret = AuthenticatedUnlock(creds, serial=args.serial, verbose=args.verbose)
540*d289c2baSAndroid Build Coastguard Worker  if ret and args.clear_factory_digest:
541*d289c2baSAndroid Build Coastguard Worker    ret = ClearFactoryPersistentDigest(serial=args.serial, verbose=args.verbose)
542*d289c2baSAndroid Build Coastguard Worker  return 0 if ret else 1
543*d289c2baSAndroid Build Coastguard Worker
544*d289c2baSAndroid Build Coastguard Worker
545*d289c2baSAndroid Build Coastguard Workerif __name__ == '__main__':
546*d289c2baSAndroid Build Coastguard Worker  sys.exit(main(sys.argv[1:]))
547