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