#!/usr/bin/env python # # Copyright 2018 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for at_auth_unlock.""" import argparse import filecmp import os import shutil import subprocess import unittest from at_auth_unlock import * from Crypto.PublicKey import RSA from unittest.mock import patch def dataPath(file): return os.path.join(os.path.dirname(__file__), 'data', file) DATA_FILE_PIK_CERTIFICATE = dataPath('atx_pik_certificate.bin') DATA_FILE_PUK_CERTIFICATE = dataPath('atx_puk_certificate.bin') DATA_FILE_PUK_KEY = dataPath('testkey_atx_puk.pem') DATA_FILE_UNLOCK_CHALLENGE = dataPath('atx_unlock_challenge.bin') DATA_FILE_UNLOCK_CREDENTIAL = dataPath('atx_unlock_credential.bin') def createTempZip(contents): tempzip = tempfile.NamedTemporaryFile() with zipfile.ZipFile(tempzip, 'w') as zip: for arcname in contents: zip.write(contents[arcname], arcname) return tempzip def validUnlockCredsZip(): return createTempZip({ 'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE, 'puk_certificate_v1.bin': DATA_FILE_PUK_CERTIFICATE, 'puk_v1.pem': DATA_FILE_PUK_KEY }) class UnlockCredentialsTest(unittest.TestCase): def testFromValidZipArchive(self): with validUnlockCredsZip() as zip: creds = UnlockCredentials.from_credential_archive(zip) self.assertIsNotNone(creds.intermediate_cert) self.assertIsNotNone(creds.unlock_cert) self.assertIsNotNone(creds.unlock_key) def testFromInvalidZipArchive(self): with self.assertRaises(zipfile.BadZipfile): UnlockCredentials.from_credential_archive(DATA_FILE_PUK_KEY) def testFromArchiveMissingPikCertificate(self): with createTempZip({ 'puk_certificate_v1.bin': DATA_FILE_PUK_CERTIFICATE, 'puk_v1.pem': DATA_FILE_PUK_KEY }) as zip: with self.assertRaises(ValueError): UnlockCredentials.from_credential_archive(zip) def testFromArchiveMissingPukCertificate(self): with createTempZip({ 'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE, 'puk_v1.pem': DATA_FILE_PUK_KEY }) as zip: with self.assertRaises(ValueError): UnlockCredentials.from_credential_archive(zip) def testFromArchiveMissingPuk(self): with createTempZip({ 'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE, 'puk_certificate_v1.bin': DATA_FILE_PUK_CERTIFICATE, }) as zip: with self.assertRaises(ValueError): UnlockCredentials.from_credential_archive(zip) def testFromArchiveMultiplePikCertificates(self): with createTempZip({ 'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE, 'pik_certificate_v2.bin': DATA_FILE_PIK_CERTIFICATE, 'puk_certificate_v1.bin': DATA_FILE_PUK_CERTIFICATE, 'puk_v1.pem': DATA_FILE_PUK_KEY }) as zip: with self.assertRaises(ValueError): UnlockCredentials.from_credential_archive(zip) def testFromArchiveMultiplePukCertificates(self): with createTempZip({ 'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE, 'puk_certificate_v1.bin': DATA_FILE_PUK_CERTIFICATE, 'puk_certificate_v2.bin': DATA_FILE_PUK_CERTIFICATE, 'puk_v1.pem': DATA_FILE_PUK_KEY }) as zip: with self.assertRaises(ValueError): UnlockCredentials.from_credential_archive(zip) def testFromArchiveMultiplePuks(self): with createTempZip({ 'pik_certificate_v1.bin': DATA_FILE_PIK_CERTIFICATE, 'puk_certificate_v1.bin': DATA_FILE_PUK_CERTIFICATE, 'puk_v1.pem': DATA_FILE_PUK_KEY, 'puk_v2.pem': DATA_FILE_PUK_KEY }) as zip: with self.assertRaises(ValueError): UnlockCredentials.from_credential_archive(zip) def testFromFiles(self): creds = UnlockCredentials( intermediate_cert_file=DATA_FILE_PIK_CERTIFICATE, unlock_cert_file=DATA_FILE_PUK_CERTIFICATE, unlock_key_file=DATA_FILE_PUK_KEY) self.assertIsNotNone(creds.intermediate_cert) self.assertIsNotNone(creds.unlock_cert) self.assertIsNotNone(creds.unlock_key) def testInvalidPuk(self): with self.assertRaises(ValueError): UnlockCredentials( intermediate_cert_file=DATA_FILE_PIK_CERTIFICATE, unlock_cert_file=DATA_FILE_PUK_CERTIFICATE, unlock_key_file=DATA_FILE_PUK_CERTIFICATE) def testPukNotPrivateKey(self): tempdir = tempfile.mkdtemp() try: with open(DATA_FILE_PUK_KEY, 'rb') as f: key = RSA.importKey(f.read()) pubkey = os.path.join(tempdir, 'pubkey.pub') with open(pubkey, 'wb') as f: f.write(key.publickey().exportKey()) with self.assertRaises(ValueError): UnlockCredentials( intermediate_cert_file=DATA_FILE_PIK_CERTIFICATE, unlock_cert_file=DATA_FILE_PUK_CERTIFICATE, unlock_key_file=pubkey) finally: shutil.rmtree(tempdir) def testWrongSizeCerts(self): pik_cert = DATA_FILE_PIK_CERTIFICATE tempdir = tempfile.mkdtemp() try: # Copy a valid cert and truncate a single byte from the end to create a # too-short cert. shortfile = os.path.join(tempdir, 'shortfile.bin') shutil.copy2(pik_cert, shortfile) with open(shortfile, 'ab') as f: f.seek(-1, os.SEEK_END) f.truncate() with self.assertRaises(ValueError): creds = UnlockCredentials( intermediate_cert_file=shortfile, unlock_cert_file=DATA_FILE_PUK_CERTIFICATE, unlock_key_file=DATA_FILE_PUK_KEY) with self.assertRaises(ValueError): creds = UnlockCredentials( intermediate_cert_file=DATA_FILE_PIK_CERTIFICATE, unlock_cert_file=shortfile, unlock_key_file=DATA_FILE_PUK_KEY) # Copy a valid cert and append an arbitrary byte on the end to create a # too-long cert. longfile = os.path.join(tempdir, 'longfile.bin') shutil.copy2(pik_cert, longfile) with open(longfile, 'ab') as f: f.write(b'\0') with self.assertRaises(ValueError): creds = UnlockCredentials( intermediate_cert_file=longfile, unlock_cert_file=DATA_FILE_PUK_CERTIFICATE, unlock_key_file=DATA_FILE_PUK_KEY) with self.assertRaises(ValueError): creds = UnlockCredentials( intermediate_cert_file=DATA_FILE_PIK_CERTIFICATE, unlock_cert_file=longfile, unlock_key_file=DATA_FILE_PUK_KEY) finally: shutil.rmtree(tempdir) def writeFullUnlockChallenge(out_file, product_id_hash=None): """Helper function to create a file with a full AvbAtxUnlockChallenge struct. Arguments: product_id_hash: [optional] 32 byte value to include in the challenge as the SHA256 hash of the product ID. If not provided, will default to the product ID hash from the subject of DATA_FILE_PUK_CERTIFICATE. """ if product_id_hash is None: with open(DATA_FILE_PUK_CERTIFICATE, 'rb') as f: product_id_hash = GetAtxCertificateSubject(f.read()) assert len(product_id_hash) == 32 with open(out_file, 'wb') as out: out.write(struct.pack('