xref: /aosp_15_r20/external/avb/test/at_auth_unlock_unittest.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"""Unit tests for at_auth_unlock."""
18*d289c2baSAndroid Build Coastguard Worker
19*d289c2baSAndroid Build Coastguard Workerimport argparse
20*d289c2baSAndroid Build Coastguard Workerimport filecmp
21*d289c2baSAndroid Build Coastguard Workerimport os
22*d289c2baSAndroid Build Coastguard Workerimport shutil
23*d289c2baSAndroid Build Coastguard Workerimport subprocess
24*d289c2baSAndroid Build Coastguard Workerimport unittest
25*d289c2baSAndroid Build Coastguard Worker
26*d289c2baSAndroid Build Coastguard Workerfrom at_auth_unlock import *
27*d289c2baSAndroid Build Coastguard Workerfrom Crypto.PublicKey import RSA
28*d289c2baSAndroid Build Coastguard Workerfrom unittest.mock import patch
29*d289c2baSAndroid Build Coastguard Worker
30*d289c2baSAndroid Build Coastguard Worker
31*d289c2baSAndroid Build Coastguard Workerdef dataPath(file):
32*d289c2baSAndroid Build Coastguard Worker  return os.path.join(os.path.dirname(__file__), 'data', file)
33*d289c2baSAndroid Build Coastguard Worker
34*d289c2baSAndroid Build Coastguard Worker
35*d289c2baSAndroid Build Coastguard WorkerDATA_FILE_PIK_CERTIFICATE = dataPath('cert_pik_certificate.bin')
36*d289c2baSAndroid Build Coastguard WorkerDATA_FILE_PUK_CERTIFICATE = dataPath('cert_puk_certificate.bin')
37*d289c2baSAndroid Build Coastguard WorkerDATA_FILE_PUK_KEY = dataPath('testkey_cert_puk.pem')
38*d289c2baSAndroid Build Coastguard WorkerDATA_FILE_UNLOCK_CHALLENGE = dataPath('cert_unlock_challenge.bin')
39*d289c2baSAndroid Build Coastguard WorkerDATA_FILE_UNLOCK_CREDENTIAL = dataPath('cert_unlock_credential.bin')
40*d289c2baSAndroid Build Coastguard Worker
41*d289c2baSAndroid Build Coastguard Worker
42*d289c2baSAndroid Build Coastguard Workerdef createTempZip(contents):
43*d289c2baSAndroid Build Coastguard Worker  tempzip = tempfile.NamedTemporaryFile()
44*d289c2baSAndroid Build Coastguard Worker  with zipfile.ZipFile(tempzip, 'w') as zip:
45*d289c2baSAndroid Build Coastguard Worker    for arcname in contents:
46*d289c2baSAndroid Build Coastguard Worker      zip.write(contents[arcname], arcname)
47*d289c2baSAndroid Build Coastguard Worker  return tempzip
48*d289c2baSAndroid Build Coastguard Worker
49*d289c2baSAndroid Build Coastguard Worker
50*d289c2baSAndroid Build Coastguard Workerdef validUnlockCredsZip():
51*d289c2baSAndroid Build Coastguard Worker  return createTempZip({
52*d289c2baSAndroid Build Coastguard Worker      'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
53*d289c2baSAndroid Build Coastguard Worker      'puk_certificate_v1.bin': DATA_FILE_PUK_CERTIFICATE,
54*d289c2baSAndroid Build Coastguard Worker      'puk_v1.pem': DATA_FILE_PUK_KEY
55*d289c2baSAndroid Build Coastguard Worker  })
56*d289c2baSAndroid Build Coastguard Worker
57*d289c2baSAndroid Build Coastguard Worker
58*d289c2baSAndroid Build Coastguard Workerclass UnlockCredentialsTest(unittest.TestCase):
59*d289c2baSAndroid Build Coastguard Worker
60*d289c2baSAndroid Build Coastguard Worker  def testFromValidZipArchive(self):
61*d289c2baSAndroid Build Coastguard Worker    with validUnlockCredsZip() as zip:
62*d289c2baSAndroid Build Coastguard Worker      creds = UnlockCredentials.from_credential_archive(zip)
63*d289c2baSAndroid Build Coastguard Worker      self.assertIsNotNone(creds.intermediate_cert)
64*d289c2baSAndroid Build Coastguard Worker      self.assertIsNotNone(creds.unlock_cert)
65*d289c2baSAndroid Build Coastguard Worker      self.assertIsNotNone(creds.unlock_key)
66*d289c2baSAndroid Build Coastguard Worker
67*d289c2baSAndroid Build Coastguard Worker  def testFromInvalidZipArchive(self):
68*d289c2baSAndroid Build Coastguard Worker    with self.assertRaises(zipfile.BadZipfile):
69*d289c2baSAndroid Build Coastguard Worker      UnlockCredentials.from_credential_archive(DATA_FILE_PUK_KEY)
70*d289c2baSAndroid Build Coastguard Worker
71*d289c2baSAndroid Build Coastguard Worker  def testFromArchiveMissingPikCertificate(self):
72*d289c2baSAndroid Build Coastguard Worker    with createTempZip({
73*d289c2baSAndroid Build Coastguard Worker        'puk_certificate_v1.bin': DATA_FILE_PUK_CERTIFICATE,
74*d289c2baSAndroid Build Coastguard Worker        'puk_v1.pem': DATA_FILE_PUK_KEY
75*d289c2baSAndroid Build Coastguard Worker    }) as zip:
76*d289c2baSAndroid Build Coastguard Worker      with self.assertRaises(ValueError):
77*d289c2baSAndroid Build Coastguard Worker        UnlockCredentials.from_credential_archive(zip)
78*d289c2baSAndroid Build Coastguard Worker
79*d289c2baSAndroid Build Coastguard Worker  def testFromArchiveMissingPukCertificate(self):
80*d289c2baSAndroid Build Coastguard Worker    with createTempZip({
81*d289c2baSAndroid Build Coastguard Worker        'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
82*d289c2baSAndroid Build Coastguard Worker        'puk_v1.pem': DATA_FILE_PUK_KEY
83*d289c2baSAndroid Build Coastguard Worker    }) as zip:
84*d289c2baSAndroid Build Coastguard Worker      with self.assertRaises(ValueError):
85*d289c2baSAndroid Build Coastguard Worker        UnlockCredentials.from_credential_archive(zip)
86*d289c2baSAndroid Build Coastguard Worker
87*d289c2baSAndroid Build Coastguard Worker  def testFromArchiveMissingPuk(self):
88*d289c2baSAndroid Build Coastguard Worker    with createTempZip({
89*d289c2baSAndroid Build Coastguard Worker        'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
90*d289c2baSAndroid Build Coastguard Worker        'puk_certificate_v1.bin': DATA_FILE_PUK_CERTIFICATE,
91*d289c2baSAndroid Build Coastguard Worker    }) as zip:
92*d289c2baSAndroid Build Coastguard Worker      with self.assertRaises(ValueError):
93*d289c2baSAndroid Build Coastguard Worker        UnlockCredentials.from_credential_archive(zip)
94*d289c2baSAndroid Build Coastguard Worker
95*d289c2baSAndroid Build Coastguard Worker  def testFromArchiveMultiplePikCertificates(self):
96*d289c2baSAndroid Build Coastguard Worker    with createTempZip({
97*d289c2baSAndroid Build Coastguard Worker        'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
98*d289c2baSAndroid Build Coastguard Worker        'pik_certificate_v2.bin': DATA_FILE_PIK_CERTIFICATE,
99*d289c2baSAndroid Build Coastguard Worker        'puk_certificate_v1.bin': DATA_FILE_PUK_CERTIFICATE,
100*d289c2baSAndroid Build Coastguard Worker        'puk_v1.pem': DATA_FILE_PUK_KEY
101*d289c2baSAndroid Build Coastguard Worker    }) as zip:
102*d289c2baSAndroid Build Coastguard Worker      with self.assertRaises(ValueError):
103*d289c2baSAndroid Build Coastguard Worker        UnlockCredentials.from_credential_archive(zip)
104*d289c2baSAndroid Build Coastguard Worker
105*d289c2baSAndroid Build Coastguard Worker  def testFromArchiveMultiplePukCertificates(self):
106*d289c2baSAndroid Build Coastguard Worker    with createTempZip({
107*d289c2baSAndroid Build Coastguard Worker        'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
108*d289c2baSAndroid Build Coastguard Worker        'puk_certificate_v1.bin': DATA_FILE_PUK_CERTIFICATE,
109*d289c2baSAndroid Build Coastguard Worker        'puk_certificate_v2.bin': DATA_FILE_PUK_CERTIFICATE,
110*d289c2baSAndroid Build Coastguard Worker        'puk_v1.pem': DATA_FILE_PUK_KEY
111*d289c2baSAndroid Build Coastguard Worker    }) as zip:
112*d289c2baSAndroid Build Coastguard Worker      with self.assertRaises(ValueError):
113*d289c2baSAndroid Build Coastguard Worker        UnlockCredentials.from_credential_archive(zip)
114*d289c2baSAndroid Build Coastguard Worker
115*d289c2baSAndroid Build Coastguard Worker  def testFromArchiveMultiplePuks(self):
116*d289c2baSAndroid Build Coastguard Worker    with createTempZip({
117*d289c2baSAndroid Build Coastguard Worker        'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
118*d289c2baSAndroid Build Coastguard Worker        'puk_certificate_v1.bin': DATA_FILE_PUK_CERTIFICATE,
119*d289c2baSAndroid Build Coastguard Worker        'puk_v1.pem': DATA_FILE_PUK_KEY,
120*d289c2baSAndroid Build Coastguard Worker        'puk_v2.pem': DATA_FILE_PUK_KEY
121*d289c2baSAndroid Build Coastguard Worker    }) as zip:
122*d289c2baSAndroid Build Coastguard Worker      with self.assertRaises(ValueError):
123*d289c2baSAndroid Build Coastguard Worker        UnlockCredentials.from_credential_archive(zip)
124*d289c2baSAndroid Build Coastguard Worker
125*d289c2baSAndroid Build Coastguard Worker  def testFromFiles(self):
126*d289c2baSAndroid Build Coastguard Worker    creds = UnlockCredentials(
127*d289c2baSAndroid Build Coastguard Worker        intermediate_cert_file=DATA_FILE_PIK_CERTIFICATE,
128*d289c2baSAndroid Build Coastguard Worker        unlock_cert_file=DATA_FILE_PUK_CERTIFICATE,
129*d289c2baSAndroid Build Coastguard Worker        unlock_key_file=DATA_FILE_PUK_KEY)
130*d289c2baSAndroid Build Coastguard Worker    self.assertIsNotNone(creds.intermediate_cert)
131*d289c2baSAndroid Build Coastguard Worker    self.assertIsNotNone(creds.unlock_cert)
132*d289c2baSAndroid Build Coastguard Worker    self.assertIsNotNone(creds.unlock_key)
133*d289c2baSAndroid Build Coastguard Worker
134*d289c2baSAndroid Build Coastguard Worker  def testInvalidPuk(self):
135*d289c2baSAndroid Build Coastguard Worker    with self.assertRaises(ValueError):
136*d289c2baSAndroid Build Coastguard Worker      UnlockCredentials(
137*d289c2baSAndroid Build Coastguard Worker          intermediate_cert_file=DATA_FILE_PIK_CERTIFICATE,
138*d289c2baSAndroid Build Coastguard Worker          unlock_cert_file=DATA_FILE_PUK_CERTIFICATE,
139*d289c2baSAndroid Build Coastguard Worker          unlock_key_file=DATA_FILE_PUK_CERTIFICATE)
140*d289c2baSAndroid Build Coastguard Worker
141*d289c2baSAndroid Build Coastguard Worker  def testPukNotPrivateKey(self):
142*d289c2baSAndroid Build Coastguard Worker    tempdir = tempfile.mkdtemp()
143*d289c2baSAndroid Build Coastguard Worker    try:
144*d289c2baSAndroid Build Coastguard Worker      with open(DATA_FILE_PUK_KEY, 'rb') as f:
145*d289c2baSAndroid Build Coastguard Worker        key = RSA.importKey(f.read())
146*d289c2baSAndroid Build Coastguard Worker      pubkey = os.path.join(tempdir, 'pubkey.pub')
147*d289c2baSAndroid Build Coastguard Worker      with open(pubkey, 'wb') as f:
148*d289c2baSAndroid Build Coastguard Worker        f.write(key.publickey().exportKey())
149*d289c2baSAndroid Build Coastguard Worker      with self.assertRaises(ValueError):
150*d289c2baSAndroid Build Coastguard Worker        UnlockCredentials(
151*d289c2baSAndroid Build Coastguard Worker            intermediate_cert_file=DATA_FILE_PIK_CERTIFICATE,
152*d289c2baSAndroid Build Coastguard Worker            unlock_cert_file=DATA_FILE_PUK_CERTIFICATE,
153*d289c2baSAndroid Build Coastguard Worker            unlock_key_file=pubkey)
154*d289c2baSAndroid Build Coastguard Worker    finally:
155*d289c2baSAndroid Build Coastguard Worker      shutil.rmtree(tempdir)
156*d289c2baSAndroid Build Coastguard Worker
157*d289c2baSAndroid Build Coastguard Worker  def testWrongSizeCerts(self):
158*d289c2baSAndroid Build Coastguard Worker    pik_cert = DATA_FILE_PIK_CERTIFICATE
159*d289c2baSAndroid Build Coastguard Worker    tempdir = tempfile.mkdtemp()
160*d289c2baSAndroid Build Coastguard Worker    try:
161*d289c2baSAndroid Build Coastguard Worker      # Copy a valid cert and truncate a single byte from the end to create a
162*d289c2baSAndroid Build Coastguard Worker      # too-short cert.
163*d289c2baSAndroid Build Coastguard Worker      shortfile = os.path.join(tempdir, 'shortfile.bin')
164*d289c2baSAndroid Build Coastguard Worker      shutil.copy2(pik_cert, shortfile)
165*d289c2baSAndroid Build Coastguard Worker      with open(shortfile, 'ab') as f:
166*d289c2baSAndroid Build Coastguard Worker        f.seek(-1, os.SEEK_END)
167*d289c2baSAndroid Build Coastguard Worker        f.truncate()
168*d289c2baSAndroid Build Coastguard Worker      with self.assertRaises(ValueError):
169*d289c2baSAndroid Build Coastguard Worker        creds = UnlockCredentials(
170*d289c2baSAndroid Build Coastguard Worker            intermediate_cert_file=shortfile,
171*d289c2baSAndroid Build Coastguard Worker            unlock_cert_file=DATA_FILE_PUK_CERTIFICATE,
172*d289c2baSAndroid Build Coastguard Worker            unlock_key_file=DATA_FILE_PUK_KEY)
173*d289c2baSAndroid Build Coastguard Worker      with self.assertRaises(ValueError):
174*d289c2baSAndroid Build Coastguard Worker        creds = UnlockCredentials(
175*d289c2baSAndroid Build Coastguard Worker            intermediate_cert_file=DATA_FILE_PIK_CERTIFICATE,
176*d289c2baSAndroid Build Coastguard Worker            unlock_cert_file=shortfile,
177*d289c2baSAndroid Build Coastguard Worker            unlock_key_file=DATA_FILE_PUK_KEY)
178*d289c2baSAndroid Build Coastguard Worker
179*d289c2baSAndroid Build Coastguard Worker      # Copy a valid cert and append an arbitrary byte on the end to create a
180*d289c2baSAndroid Build Coastguard Worker      # too-long cert.
181*d289c2baSAndroid Build Coastguard Worker      longfile = os.path.join(tempdir, 'longfile.bin')
182*d289c2baSAndroid Build Coastguard Worker      shutil.copy2(pik_cert, longfile)
183*d289c2baSAndroid Build Coastguard Worker      with open(longfile, 'ab') as f:
184*d289c2baSAndroid Build Coastguard Worker        f.write(b'\0')
185*d289c2baSAndroid Build Coastguard Worker      with self.assertRaises(ValueError):
186*d289c2baSAndroid Build Coastguard Worker        creds = UnlockCredentials(
187*d289c2baSAndroid Build Coastguard Worker            intermediate_cert_file=longfile,
188*d289c2baSAndroid Build Coastguard Worker            unlock_cert_file=DATA_FILE_PUK_CERTIFICATE,
189*d289c2baSAndroid Build Coastguard Worker            unlock_key_file=DATA_FILE_PUK_KEY)
190*d289c2baSAndroid Build Coastguard Worker      with self.assertRaises(ValueError):
191*d289c2baSAndroid Build Coastguard Worker        creds = UnlockCredentials(
192*d289c2baSAndroid Build Coastguard Worker            intermediate_cert_file=DATA_FILE_PIK_CERTIFICATE,
193*d289c2baSAndroid Build Coastguard Worker            unlock_cert_file=longfile,
194*d289c2baSAndroid Build Coastguard Worker            unlock_key_file=DATA_FILE_PUK_KEY)
195*d289c2baSAndroid Build Coastguard Worker    finally:
196*d289c2baSAndroid Build Coastguard Worker      shutil.rmtree(tempdir)
197*d289c2baSAndroid Build Coastguard Worker
198*d289c2baSAndroid Build Coastguard Worker
199*d289c2baSAndroid Build Coastguard Workerdef writeFullUnlockChallenge(out_file, product_id_hash=None):
200*d289c2baSAndroid Build Coastguard Worker  """Helper function to create a file with a full AvbCertUnlockChallenge struct.
201*d289c2baSAndroid Build Coastguard Worker
202*d289c2baSAndroid Build Coastguard Worker  Arguments:
203*d289c2baSAndroid Build Coastguard Worker    product_id_hash: [optional] 32 byte value to include in the challenge as the
204*d289c2baSAndroid Build Coastguard Worker      SHA256 hash of the product ID. If not provided, will default to the
205*d289c2baSAndroid Build Coastguard Worker      product ID hash from the subject of DATA_FILE_PUK_CERTIFICATE.
206*d289c2baSAndroid Build Coastguard Worker  """
207*d289c2baSAndroid Build Coastguard Worker  if product_id_hash is None:
208*d289c2baSAndroid Build Coastguard Worker    with open(DATA_FILE_PUK_CERTIFICATE, 'rb') as f:
209*d289c2baSAndroid Build Coastguard Worker      product_id_hash = GetCertCertificateSubject(f.read())
210*d289c2baSAndroid Build Coastguard Worker  assert len(product_id_hash) == 32
211*d289c2baSAndroid Build Coastguard Worker
212*d289c2baSAndroid Build Coastguard Worker  with open(out_file, 'wb') as out:
213*d289c2baSAndroid Build Coastguard Worker    out.write(struct.pack('<I', 1))
214*d289c2baSAndroid Build Coastguard Worker    out.write(product_id_hash)
215*d289c2baSAndroid Build Coastguard Worker    with open(DATA_FILE_UNLOCK_CHALLENGE, 'rb') as f:
216*d289c2baSAndroid Build Coastguard Worker      out.write(f.read())
217*d289c2baSAndroid Build Coastguard Worker
218*d289c2baSAndroid Build Coastguard Worker
219*d289c2baSAndroid Build Coastguard Workerclass MakeCertUnlockCredentialTest(unittest.TestCase):
220*d289c2baSAndroid Build Coastguard Worker
221*d289c2baSAndroid Build Coastguard Worker  def testCredentialIsCorrect(self):
222*d289c2baSAndroid Build Coastguard Worker    with validUnlockCredsZip() as zip:
223*d289c2baSAndroid Build Coastguard Worker      creds = UnlockCredentials.from_credential_archive(zip)
224*d289c2baSAndroid Build Coastguard Worker
225*d289c2baSAndroid Build Coastguard Worker      tempdir = tempfile.mkdtemp()
226*d289c2baSAndroid Build Coastguard Worker      try:
227*d289c2baSAndroid Build Coastguard Worker        challenge_file = os.path.join(tempdir, 'challenge')
228*d289c2baSAndroid Build Coastguard Worker        writeFullUnlockChallenge(challenge_file)
229*d289c2baSAndroid Build Coastguard Worker        challenge = UnlockChallenge(challenge_file)
230*d289c2baSAndroid Build Coastguard Worker        out_cred = os.path.join(tempdir, 'credential')
231*d289c2baSAndroid Build Coastguard Worker
232*d289c2baSAndroid Build Coastguard Worker        # Compare unlock credential generated by function with one generated
233*d289c2baSAndroid Build Coastguard Worker        # using 'avbtool make_cert_unlock_credential', to check correctness.
234*d289c2baSAndroid Build Coastguard Worker        MakeCertUnlockCredential(creds, challenge, out_cred)
235*d289c2baSAndroid Build Coastguard Worker        self.assertTrue(filecmp.cmp(out_cred, DATA_FILE_UNLOCK_CREDENTIAL))
236*d289c2baSAndroid Build Coastguard Worker      finally:
237*d289c2baSAndroid Build Coastguard Worker        shutil.rmtree(tempdir)
238*d289c2baSAndroid Build Coastguard Worker
239*d289c2baSAndroid Build Coastguard Worker  def testWrongChallengeSize(self):
240*d289c2baSAndroid Build Coastguard Worker    with validUnlockCredsZip() as zip:
241*d289c2baSAndroid Build Coastguard Worker      creds = UnlockCredentials.from_credential_archive(zip)
242*d289c2baSAndroid Build Coastguard Worker
243*d289c2baSAndroid Build Coastguard Worker      tempdir = tempfile.mkdtemp()
244*d289c2baSAndroid Build Coastguard Worker      try:
245*d289c2baSAndroid Build Coastguard Worker        out_cred = os.path.join(tempdir, 'credential')
246*d289c2baSAndroid Build Coastguard Worker
247*d289c2baSAndroid Build Coastguard Worker        # The bundled unlock challenge is just the 16 byte challenge, not the
248*d289c2baSAndroid Build Coastguard Worker        # full AvbCertUnlockChallenge like this expects.
249*d289c2baSAndroid Build Coastguard Worker        with self.assertRaises(ValueError):
250*d289c2baSAndroid Build Coastguard Worker          challenge = UnlockChallenge(DATA_FILE_UNLOCK_CHALLENGE)
251*d289c2baSAndroid Build Coastguard Worker          MakeCertUnlockCredential(creds, challenge, out_cred)
252*d289c2baSAndroid Build Coastguard Worker      finally:
253*d289c2baSAndroid Build Coastguard Worker        shutil.rmtree(tempdir)
254*d289c2baSAndroid Build Coastguard Worker
255*d289c2baSAndroid Build Coastguard Worker
256*d289c2baSAndroid Build Coastguard Workerdef makeFastbootCommandFake(testcase,
257*d289c2baSAndroid Build Coastguard Worker                            expect_serial=None,
258*d289c2baSAndroid Build Coastguard Worker                            error_on_command_number=None,
259*d289c2baSAndroid Build Coastguard Worker                            product_id_hash=None,
260*d289c2baSAndroid Build Coastguard Worker                            stay_locked=False):
261*d289c2baSAndroid Build Coastguard Worker  """Construct a fake fastboot command handler, to be used with unitttest.mock.Mock.side_effect.
262*d289c2baSAndroid Build Coastguard Worker
263*d289c2baSAndroid Build Coastguard Worker  This can be used to create a callable that acts as a fake for a real device
264*d289c2baSAndroid Build Coastguard Worker  responding to the fastboot commands involved in an authenticated unlock. The
265*d289c2baSAndroid Build Coastguard Worker  returned callback is intended to be used with unittest.mock.Mock.side_effect.
266*d289c2baSAndroid Build Coastguard Worker  There are a number of optional arguments here that can be used to customize
267*d289c2baSAndroid Build Coastguard Worker  the behavior of the fake for a specific test.
268*d289c2baSAndroid Build Coastguard Worker
269*d289c2baSAndroid Build Coastguard Worker  Arguments:
270*d289c2baSAndroid Build Coastguard Worker    testcase: unittest.TestCase object for the associated test
271*d289c2baSAndroid Build Coastguard Worker    expect_serial: [optional] Expect (and assert) that the fastboot command
272*d289c2baSAndroid Build Coastguard Worker      specifies a specific device serial to communicate with.
273*d289c2baSAndroid Build Coastguard Worker    error_on_command_number: [optional] Return a fastboot error (non-zero exit
274*d289c2baSAndroid Build Coastguard Worker      code) on the nth (0-based) command handled.
275*d289c2baSAndroid Build Coastguard Worker    stay_locked: [optional] Make the fake report that the device is still locked
276*d289c2baSAndroid Build Coastguard Worker      after an otherwise successful unlock attempt.
277*d289c2baSAndroid Build Coastguard Worker  """
278*d289c2baSAndroid Build Coastguard Worker
279*d289c2baSAndroid Build Coastguard Worker  def handler(args, *extraArgs, **kwargs):
280*d289c2baSAndroid Build Coastguard Worker    if error_on_command_number is not None:
281*d289c2baSAndroid Build Coastguard Worker      handler.command_counter += 1
282*d289c2baSAndroid Build Coastguard Worker      if handler.command_counter - 1 == error_on_command_number:
283*d289c2baSAndroid Build Coastguard Worker        raise subprocess.CalledProcessError(
284*d289c2baSAndroid Build Coastguard Worker            returncode=1, cmd=args, output=b'Fake: ERROR')
285*d289c2baSAndroid Build Coastguard Worker
286*d289c2baSAndroid Build Coastguard Worker    testcase.assertEqual(args.pop(0), 'fastboot')
287*d289c2baSAndroid Build Coastguard Worker    if expect_serial is not None:
288*d289c2baSAndroid Build Coastguard Worker      # This is a bit fragile in that, in reality, fastboot allows '-s SERIAL'
289*d289c2baSAndroid Build Coastguard Worker      # to not just be the first arguments, but it works for this use case.
290*d289c2baSAndroid Build Coastguard Worker      testcase.assertEqual(args.pop(0), '-s')
291*d289c2baSAndroid Build Coastguard Worker      testcase.assertEqual(args.pop(0), expect_serial)
292*d289c2baSAndroid Build Coastguard Worker
293*d289c2baSAndroid Build Coastguard Worker    if args[0:2] == ['oem', 'at-get-vboot-unlock-challenge']:
294*d289c2baSAndroid Build Coastguard Worker      handler.challenge_staged = True
295*d289c2baSAndroid Build Coastguard Worker    elif args[0] == 'get_staged':
296*d289c2baSAndroid Build Coastguard Worker      if not handler.challenge_staged:
297*d289c2baSAndroid Build Coastguard Worker        raise subprocess.CalledProcessError(
298*d289c2baSAndroid Build Coastguard Worker            returncode=1, cmd=args, output=b'Fake: No data staged')
299*d289c2baSAndroid Build Coastguard Worker
300*d289c2baSAndroid Build Coastguard Worker      writeFullUnlockChallenge(args[1], product_id_hash=product_id_hash)
301*d289c2baSAndroid Build Coastguard Worker      handler.challenge_staged = False
302*d289c2baSAndroid Build Coastguard Worker    elif args[0] == 'stage':
303*d289c2baSAndroid Build Coastguard Worker      handler.staged_file = args[1]
304*d289c2baSAndroid Build Coastguard Worker    elif args[0:2] == ['oem', 'at-unlock-vboot']:
305*d289c2baSAndroid Build Coastguard Worker      if handler.staged_file is None:
306*d289c2baSAndroid Build Coastguard Worker        raise subprocess.CalledProcessError(
307*d289c2baSAndroid Build Coastguard Worker            returncode=1, cmd=args, output=b'Fake: No unlock credential staged')
308*d289c2baSAndroid Build Coastguard Worker
309*d289c2baSAndroid Build Coastguard Worker      # Validate the unlock credential as if this were a test key locked device,
310*d289c2baSAndroid Build Coastguard Worker      # which implies tests that want a successful unlock need to be set up to
311*d289c2baSAndroid Build Coastguard Worker      # use DATA_FILE_PUK_KEY to sign the challenge. Credentials generated using
312*d289c2baSAndroid Build Coastguard Worker      # other keys will be properly rejected.
313*d289c2baSAndroid Build Coastguard Worker      if not filecmp.cmp(handler.staged_file, DATA_FILE_UNLOCK_CREDENTIAL):
314*d289c2baSAndroid Build Coastguard Worker        raise subprocess.CalledProcessError(
315*d289c2baSAndroid Build Coastguard Worker            returncode=1, cmd=args, output=b'Fake: Incorrect unlock credential')
316*d289c2baSAndroid Build Coastguard Worker
317*d289c2baSAndroid Build Coastguard Worker      handler.locked = True if stay_locked else False
318*d289c2baSAndroid Build Coastguard Worker    elif args[0:2] == ['getvar', 'at-vboot-state']:
319*d289c2baSAndroid Build Coastguard Worker      return b'avb-locked: ' + (b'1' if handler.locked else b'0')
320*d289c2baSAndroid Build Coastguard Worker    return b'Fake: OK'
321*d289c2baSAndroid Build Coastguard Worker
322*d289c2baSAndroid Build Coastguard Worker  handler.command_counter = 0
323*d289c2baSAndroid Build Coastguard Worker  handler.challenge_staged = False
324*d289c2baSAndroid Build Coastguard Worker  handler.staged_file = None
325*d289c2baSAndroid Build Coastguard Worker  handler.locked = True
326*d289c2baSAndroid Build Coastguard Worker  return handler
327*d289c2baSAndroid Build Coastguard Worker
328*d289c2baSAndroid Build Coastguard Worker
329*d289c2baSAndroid Build Coastguard Workerclass AuthenticatedUnlockTest(unittest.TestCase):
330*d289c2baSAndroid Build Coastguard Worker
331*d289c2baSAndroid Build Coastguard Worker  @patch('subprocess.check_output')
332*d289c2baSAndroid Build Coastguard Worker  def testUnlockWithZipArchive(self, mock_subp_check_output):
333*d289c2baSAndroid Build Coastguard Worker    with validUnlockCredsZip() as zip:
334*d289c2baSAndroid Build Coastguard Worker      mock_subp_check_output.side_effect = makeFastbootCommandFake(self)
335*d289c2baSAndroid Build Coastguard Worker      self.assertEqual(main([zip.name]), 0)
336*d289c2baSAndroid Build Coastguard Worker      self.assertNotEqual(mock_subp_check_output.call_count, 0)
337*d289c2baSAndroid Build Coastguard Worker
338*d289c2baSAndroid Build Coastguard Worker  @patch('subprocess.check_output')
339*d289c2baSAndroid Build Coastguard Worker  def testUnlockDeviceBySerial(self, mock_subp_check_output):
340*d289c2baSAndroid Build Coastguard Worker    with validUnlockCredsZip() as zip:
341*d289c2baSAndroid Build Coastguard Worker      SERIAL = 'abcde12345'
342*d289c2baSAndroid Build Coastguard Worker      mock_subp_check_output.side_effect = makeFastbootCommandFake(
343*d289c2baSAndroid Build Coastguard Worker          self, expect_serial=SERIAL)
344*d289c2baSAndroid Build Coastguard Worker      self.assertEqual(main([zip.name, '-s', SERIAL]), 0)
345*d289c2baSAndroid Build Coastguard Worker      self.assertNotEqual(mock_subp_check_output.call_count, 0)
346*d289c2baSAndroid Build Coastguard Worker
347*d289c2baSAndroid Build Coastguard Worker  @patch('subprocess.check_output')
348*d289c2baSAndroid Build Coastguard Worker  def testUnlockWithIndividualFiles(self, mock_subp_check_output):
349*d289c2baSAndroid Build Coastguard Worker    mock_subp_check_output.side_effect = makeFastbootCommandFake(self)
350*d289c2baSAndroid Build Coastguard Worker    self.assertEqual(
351*d289c2baSAndroid Build Coastguard Worker        main([
352*d289c2baSAndroid Build Coastguard Worker            '--pik_cert', DATA_FILE_PIK_CERTIFICATE, '--puk_cert',
353*d289c2baSAndroid Build Coastguard Worker            DATA_FILE_PUK_CERTIFICATE, '--puk', DATA_FILE_PUK_KEY
354*d289c2baSAndroid Build Coastguard Worker        ]), 0)
355*d289c2baSAndroid Build Coastguard Worker    self.assertNotEqual(mock_subp_check_output.call_count, 0)
356*d289c2baSAndroid Build Coastguard Worker
357*d289c2baSAndroid Build Coastguard Worker  @patch('subprocess.check_output')
358*d289c2baSAndroid Build Coastguard Worker  def testFastbootError(self, mock_subp_check_output):
359*d289c2baSAndroid Build Coastguard Worker    """Verify that errors are handled properly if fastboot commands error out."""
360*d289c2baSAndroid Build Coastguard Worker    with validUnlockCredsZip() as zip:
361*d289c2baSAndroid Build Coastguard Worker      for n in range(5):
362*d289c2baSAndroid Build Coastguard Worker        mock_subp_check_output.reset_mock()
363*d289c2baSAndroid Build Coastguard Worker        mock_subp_check_output.side_effect = makeFastbootCommandFake(
364*d289c2baSAndroid Build Coastguard Worker            self, error_on_command_number=n)
365*d289c2baSAndroid Build Coastguard Worker        self.assertNotEqual(main([zip.name]), 0)
366*d289c2baSAndroid Build Coastguard Worker        self.assertNotEqual(mock_subp_check_output.call_count, 0)
367*d289c2baSAndroid Build Coastguard Worker
368*d289c2baSAndroid Build Coastguard Worker  @patch('subprocess.check_output')
369*d289c2baSAndroid Build Coastguard Worker  def testDoesntActuallyUnlock(self, mock_subp_check_output):
370*d289c2baSAndroid Build Coastguard Worker    """Verify fails if fake set to not actually unlock."""
371*d289c2baSAndroid Build Coastguard Worker    with validUnlockCredsZip() as zip:
372*d289c2baSAndroid Build Coastguard Worker      mock_subp_check_output.side_effect = makeFastbootCommandFake(
373*d289c2baSAndroid Build Coastguard Worker          self, stay_locked=True)
374*d289c2baSAndroid Build Coastguard Worker      self.assertNotEqual(main([zip.name]), 0)
375*d289c2baSAndroid Build Coastguard Worker      self.assertNotEqual(mock_subp_check_output.call_count, 0)
376*d289c2baSAndroid Build Coastguard Worker
377*d289c2baSAndroid Build Coastguard Worker  @patch('subprocess.check_output')
378*d289c2baSAndroid Build Coastguard Worker  def testNoCredentialsMatchDeviceProductID(self, mock_subp_check_output):
379*d289c2baSAndroid Build Coastguard Worker    """Test two cases where fake responds with a challenge that has a product ID hash which doesn't match the credentials used."""
380*d289c2baSAndroid Build Coastguard Worker    # Case 1: Change the product ID hash that the fake responds with.
381*d289c2baSAndroid Build Coastguard Worker    with validUnlockCredsZip() as zip:
382*d289c2baSAndroid Build Coastguard Worker      mock_subp_check_output.side_effect = makeFastbootCommandFake(
383*d289c2baSAndroid Build Coastguard Worker          self, product_id_hash=b'\x00' * 32)
384*d289c2baSAndroid Build Coastguard Worker      self.assertNotEqual(main([zip.name]), 0)
385*d289c2baSAndroid Build Coastguard Worker      self.assertNotEqual(mock_subp_check_output.call_count, 0)
386*d289c2baSAndroid Build Coastguard Worker
387*d289c2baSAndroid Build Coastguard Worker    # Case 2: Use credentials with a different product ID.
388*d289c2baSAndroid Build Coastguard Worker    with createTempZip({
389*d289c2baSAndroid Build Coastguard Worker        'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
390*d289c2baSAndroid Build Coastguard Worker        # Note: PIK cert used as PUK cert so subject (i.e. product ID hash) is
391*d289c2baSAndroid Build Coastguard Worker        # different
392*d289c2baSAndroid Build Coastguard Worker        'puk_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
393*d289c2baSAndroid Build Coastguard Worker        'puk_v1.pem': DATA_FILE_PUK_KEY
394*d289c2baSAndroid Build Coastguard Worker    }) as zip:
395*d289c2baSAndroid Build Coastguard Worker      mock_subp_check_output.side_effect = makeFastbootCommandFake(self)
396*d289c2baSAndroid Build Coastguard Worker      self.assertNotEqual(main([zip.name]), 0)
397*d289c2baSAndroid Build Coastguard Worker      self.assertNotEqual(mock_subp_check_output.call_count, 0)
398*d289c2baSAndroid Build Coastguard Worker
399*d289c2baSAndroid Build Coastguard Worker  @patch('subprocess.check_output')
400*d289c2baSAndroid Build Coastguard Worker  def testMatchingCredentialSelectedFromZipArchives(self,
401*d289c2baSAndroid Build Coastguard Worker                                                    mock_subp_check_output):
402*d289c2baSAndroid Build Coastguard Worker    """Test correct credential based on product ID hash used if multiple provided directly through arguments."""
403*d289c2baSAndroid Build Coastguard Worker    with validUnlockCredsZip() as correctCreds, createTempZip({
404*d289c2baSAndroid Build Coastguard Worker        'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
405*d289c2baSAndroid Build Coastguard Worker        # Note: PIK cert used as PUK cert so subject (i.e. product ID hash)
406*d289c2baSAndroid Build Coastguard Worker        # doesn't match
407*d289c2baSAndroid Build Coastguard Worker        'puk_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
408*d289c2baSAndroid Build Coastguard Worker        'puk_v1.pem': DATA_FILE_PUK_KEY
409*d289c2baSAndroid Build Coastguard Worker    }) as wrongCreds:
410*d289c2baSAndroid Build Coastguard Worker      mock_subp_check_output.side_effect = makeFastbootCommandFake(self)
411*d289c2baSAndroid Build Coastguard Worker      self.assertEqual(main([wrongCreds.name, correctCreds.name]), 0)
412*d289c2baSAndroid Build Coastguard Worker      self.assertNotEqual(mock_subp_check_output.call_count, 0)
413*d289c2baSAndroid Build Coastguard Worker
414*d289c2baSAndroid Build Coastguard Worker  @patch('subprocess.check_output')
415*d289c2baSAndroid Build Coastguard Worker  def testMatchingCredentialSelectedFromDirectory(self, mock_subp_check_output):
416*d289c2baSAndroid Build Coastguard Worker    """Test correct credential based on product ID hash used if multiple provided indirectly through a directory argument."""
417*d289c2baSAndroid Build Coastguard Worker    with validUnlockCredsZip() as correctCreds, createTempZip({
418*d289c2baSAndroid Build Coastguard Worker        'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
419*d289c2baSAndroid Build Coastguard Worker        # Note: PIK cert used as PUK cert so subject (i.e. product ID hash)
420*d289c2baSAndroid Build Coastguard Worker        # doesn't match
421*d289c2baSAndroid Build Coastguard Worker        'puk_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
422*d289c2baSAndroid Build Coastguard Worker        'puk_v1.pem': DATA_FILE_PUK_KEY
423*d289c2baSAndroid Build Coastguard Worker    }) as wrongCreds:
424*d289c2baSAndroid Build Coastguard Worker      tempdir = tempfile.mkdtemp()
425*d289c2baSAndroid Build Coastguard Worker      try:
426*d289c2baSAndroid Build Coastguard Worker        shutil.copy2(correctCreds.name, tempdir)
427*d289c2baSAndroid Build Coastguard Worker        shutil.copy2(wrongCreds.name, tempdir)
428*d289c2baSAndroid Build Coastguard Worker
429*d289c2baSAndroid Build Coastguard Worker        mock_subp_check_output.side_effect = makeFastbootCommandFake(self)
430*d289c2baSAndroid Build Coastguard Worker        self.assertEqual(main([tempdir]), 0)
431*d289c2baSAndroid Build Coastguard Worker        self.assertNotEqual(mock_subp_check_output.call_count, 0)
432*d289c2baSAndroid Build Coastguard Worker      finally:
433*d289c2baSAndroid Build Coastguard Worker        shutil.rmtree(tempdir)
434*d289c2baSAndroid Build Coastguard Worker
435*d289c2baSAndroid Build Coastguard Worker  @patch('subprocess.check_output')
436*d289c2baSAndroid Build Coastguard Worker  def testMatchingCredentialSelectedFromEither(self, mock_subp_check_output):
437*d289c2baSAndroid Build Coastguard Worker    """Test correct credential based on product ID hash used if arguments give some combination of file and directory arguments."""
438*d289c2baSAndroid Build Coastguard Worker    with validUnlockCredsZip() as correctCreds, createTempZip({
439*d289c2baSAndroid Build Coastguard Worker        'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
440*d289c2baSAndroid Build Coastguard Worker        # Note: PIK cert used as PUK cert so subject (i.e. product ID hash)
441*d289c2baSAndroid Build Coastguard Worker        # doesn't match
442*d289c2baSAndroid Build Coastguard Worker        'puk_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE,
443*d289c2baSAndroid Build Coastguard Worker        'puk_v1.pem': DATA_FILE_PUK_KEY
444*d289c2baSAndroid Build Coastguard Worker    }) as wrongCreds:
445*d289c2baSAndroid Build Coastguard Worker      # Case 1: Correct creds in directory, wrong in file arg
446*d289c2baSAndroid Build Coastguard Worker      tempdir = tempfile.mkdtemp()
447*d289c2baSAndroid Build Coastguard Worker      try:
448*d289c2baSAndroid Build Coastguard Worker        shutil.copy2(correctCreds.name, tempdir)
449*d289c2baSAndroid Build Coastguard Worker
450*d289c2baSAndroid Build Coastguard Worker        mock_subp_check_output.side_effect = makeFastbootCommandFake(self)
451*d289c2baSAndroid Build Coastguard Worker        self.assertEqual(main([wrongCreds.name, tempdir]), 0)
452*d289c2baSAndroid Build Coastguard Worker        self.assertNotEqual(mock_subp_check_output.call_count, 0)
453*d289c2baSAndroid Build Coastguard Worker
454*d289c2baSAndroid Build Coastguard Worker      finally:
455*d289c2baSAndroid Build Coastguard Worker        shutil.rmtree(tempdir)
456*d289c2baSAndroid Build Coastguard Worker
457*d289c2baSAndroid Build Coastguard Worker      # Case 2: Correct creds in file arg, wrong in directory
458*d289c2baSAndroid Build Coastguard Worker      tempdir = tempfile.mkdtemp()
459*d289c2baSAndroid Build Coastguard Worker      try:
460*d289c2baSAndroid Build Coastguard Worker        shutil.copy2(wrongCreds.name, tempdir)
461*d289c2baSAndroid Build Coastguard Worker
462*d289c2baSAndroid Build Coastguard Worker        mock_subp_check_output.side_effect = makeFastbootCommandFake(self)
463*d289c2baSAndroid Build Coastguard Worker        self.assertEqual(main([tempdir, correctCreds.name]), 0)
464*d289c2baSAndroid Build Coastguard Worker        self.assertNotEqual(mock_subp_check_output.call_count, 0)
465*d289c2baSAndroid Build Coastguard Worker
466*d289c2baSAndroid Build Coastguard Worker        # Case 2: Correct creds in file arg, wrong in directory
467*d289c2baSAndroid Build Coastguard Worker      finally:
468*d289c2baSAndroid Build Coastguard Worker        shutil.rmtree(tempdir)
469*d289c2baSAndroid Build Coastguard Worker
470*d289c2baSAndroid Build Coastguard Worker  @patch('argparse.ArgumentParser.error')
471*d289c2baSAndroid Build Coastguard Worker  def testArgparseDirectoryWithNoCredentials(self, mock_parser_error):
472*d289c2baSAndroid Build Coastguard Worker    """Test """
473*d289c2baSAndroid Build Coastguard Worker    tempdir = tempfile.mkdtemp()
474*d289c2baSAndroid Build Coastguard Worker    try:
475*d289c2baSAndroid Build Coastguard Worker      # Make sure random files are ignored.
476*d289c2baSAndroid Build Coastguard Worker      with open(os.path.join(tempdir, 'so_random'), 'w') as f:
477*d289c2baSAndroid Build Coastguard Worker        f.write("I'm a random file")
478*d289c2baSAndroid Build Coastguard Worker
479*d289c2baSAndroid Build Coastguard Worker      mock_parser_error.side_effect = ValueError('ArgumentParser.error')
480*d289c2baSAndroid Build Coastguard Worker      with self.assertRaises(ValueError):
481*d289c2baSAndroid Build Coastguard Worker        main([tempdir])
482*d289c2baSAndroid Build Coastguard Worker      self.assertEqual(mock_parser_error.call_count, 1)
483*d289c2baSAndroid Build Coastguard Worker    finally:
484*d289c2baSAndroid Build Coastguard Worker      shutil.rmtree(tempdir)
485*d289c2baSAndroid Build Coastguard Worker
486*d289c2baSAndroid Build Coastguard Worker  @patch('argparse.ArgumentParser.error')
487*d289c2baSAndroid Build Coastguard Worker  def testArgparseMutualExclusionArchiveAndFiles(self, mock_parser_error):
488*d289c2baSAndroid Build Coastguard Worker    mock_parser_error.side_effect = ValueError('ArgumentParser.error')
489*d289c2baSAndroid Build Coastguard Worker    with self.assertRaises(ValueError):
490*d289c2baSAndroid Build Coastguard Worker      main(['dummy.zip', '--pik_cert', DATA_FILE_PIK_CERTIFICATE])
491*d289c2baSAndroid Build Coastguard Worker    self.assertEqual(mock_parser_error.call_count, 1)
492*d289c2baSAndroid Build Coastguard Worker
493*d289c2baSAndroid Build Coastguard Worker  @patch('argparse.ArgumentParser.error')
494*d289c2baSAndroid Build Coastguard Worker  def testArgparseMutualInclusionOfFileArgs(self, mock_parser_error):
495*d289c2baSAndroid Build Coastguard Worker    mock_parser_error.side_effect = ValueError('ArgumentParser.error')
496*d289c2baSAndroid Build Coastguard Worker    with self.assertRaises(ValueError):
497*d289c2baSAndroid Build Coastguard Worker      main(['--pik_cert', 'pik_cert.bin', '--puk_cert', 'puk_cert.bin'])
498*d289c2baSAndroid Build Coastguard Worker    self.assertEqual(mock_parser_error.call_count, 1)
499*d289c2baSAndroid Build Coastguard Worker
500*d289c2baSAndroid Build Coastguard Worker    mock_parser_error.reset_mock()
501*d289c2baSAndroid Build Coastguard Worker    with self.assertRaises(ValueError):
502*d289c2baSAndroid Build Coastguard Worker      main(['--pik_cert', 'pik_cert.bin', '--puk', 'puk.pem'])
503*d289c2baSAndroid Build Coastguard Worker    self.assertEqual(mock_parser_error.call_count, 1)
504*d289c2baSAndroid Build Coastguard Worker
505*d289c2baSAndroid Build Coastguard Worker    mock_parser_error.reset_mock()
506*d289c2baSAndroid Build Coastguard Worker    with self.assertRaises(ValueError):
507*d289c2baSAndroid Build Coastguard Worker      main(['--puk_cert', 'puk_cert.bin', '--puk', 'puk.pem'])
508*d289c2baSAndroid Build Coastguard Worker    self.assertEqual(mock_parser_error.call_count, 1)
509*d289c2baSAndroid Build Coastguard Worker
510*d289c2baSAndroid Build Coastguard Worker  @patch('argparse.ArgumentParser.error')
511*d289c2baSAndroid Build Coastguard Worker  def testArgparseMissingBundleAndFiles(self, mock_parser_error):
512*d289c2baSAndroid Build Coastguard Worker    mock_parser_error.side_effect = ValueError('ArgumentParser.error')
513*d289c2baSAndroid Build Coastguard Worker    with self.assertRaises(ValueError):
514*d289c2baSAndroid Build Coastguard Worker      main(['-s', '1234abcd'])
515*d289c2baSAndroid Build Coastguard Worker    self.assertEqual(mock_parser_error.call_count, 1)
516*d289c2baSAndroid Build Coastguard Worker
517*d289c2baSAndroid Build Coastguard Worker
518*d289c2baSAndroid Build Coastguard Workerif __name__ == '__main__':
519*d289c2baSAndroid Build Coastguard Worker  unittest.main(verbosity=3)
520