def test_bad_signature(self): # Both files are downloaded, but the signature does not match the # image-master key. setup_keyrings() # Use the spare key as the blacklist, signed by itself. Since this # won't match the image-signing key, the check will fail. server_path = os.path.join(self._serverdir, 'gpg', 'blacklist.tar.xz') setup_keyring_txz('spare.gpg', 'spare.gpg', dict(type='blacklist'), server_path) with self.assertRaises(SignatureError) as cm: get_keyring('blacklist', 'gpg/blacklist.tar.xz', 'image-master') error = cm.exception # The local file name will be keyring.tar.xz in the cache directory. basename = os.path.basename self.assertEqual(basename(error.data_path), 'keyring.tar.xz') self.assertEqual(basename(error.signature_path), 'keyring.tar.xz.asc') # The crafted blacklist.tar.xz file will have an unpredictable # checksum due to tarfile variablility. with open(server_path, 'rb') as fp: checksum = hashlib.md5(fp.read()).hexdigest() self.assertEqual(error.data_checksum, checksum) # The signature file's checksum is also unpredictable. with open(server_path + '.asc', 'rb') as fp: checksum = hashlib.md5(fp.read()).hexdigest() self.assertEqual(error.signature_checksum, checksum)
def test_download_winners_signed_by_wrong_key(self): # There is a device key, but the image files are signed by the image # signing key, which according to the spec means the files are not # signed correctly. setup_keyrings() # To set up the device signing key, we need to load this channels.json # file and copy the device keyring to the server. copy('winner.channels_02.json', self._serverdir, 'channels.json') sign(os.path.join(self._serverdir, 'channels.json'), 'image-signing.gpg') setup_keyring_txz( 'device-signing.gpg', 'image-signing.gpg', dict(type='device-signing'), os.path.join(self._serverdir, 'stable', 'nexus7', 'device.tar.xz')) sign(os.path.join(self._serverdir, self._indexpath), 'device-signing.gpg') # All the downloadable files are now signed with a bogus key. setup_index('winner.index_02.json', self._serverdir, 'spare.gpg') touch_build(100) # Run the state machine until just before we download the files. state = State() state.run_until('download_files') # The next state transition will fail because of the missing signature. self.assertRaises(SignatureError, next, state) # There are no downloaded files. txtfiles = set(filename for filename in os.listdir(config.tempdir) if os.path.splitext(filename)[1] == '.txt') self.assertEqual(len(txtfiles), 0)
def test_signature_invalid(self): # The .validate() method raises a SignatureError exception with extra # information when the signature is invalid. channels_json = os.path.join(self._tmpdir, 'channels.json') copy('gpg.channels_01.json', self._tmpdir, dst=channels_json) sign(channels_json, 'device-signing.gpg') # Verify the signature with the pubkey. with temporary_directory() as tmpdir: dst = os.path.join(tmpdir, 'image-signing.tar.xz') setup_keyring_txz('image-signing.gpg', 'image-master.gpg', dict(type='image-signing'), dst) # Get the dst's checksum now, because the file will get deleted # when the tmpdir context manager exits. with open(dst, 'rb') as fp: dst_checksum = hashlib.md5(fp.read()).hexdigest() with Context(dst) as ctx: with self.assertRaises(SignatureError) as cm: ctx.validate(channels_json + '.asc', channels_json) error = cm.exception basename = os.path.basename self.assertEqual(basename(error.signature_path), 'channels.json.asc') self.assertEqual(basename(error.data_path), 'channels.json') # The contents of the signature file are not predictable. with open(channels_json + '.asc', 'rb') as fp: checksum = hashlib.md5(fp.read()).hexdigest() self.assertEqual(error.signature_checksum, checksum) self.assertEqual( error.data_checksum, '715c63fecbf44b62f9fa04a82dfa7d29') basenames = [basename(path) for path in error.keyrings] self.assertEqual(basenames, ['image-signing.tar.xz']) self.assertIsNone(error.blacklist) self.assertEqual(error.keyring_checksums, [dst_checksum]) self.assertIsNone(error.blacklist_checksum)
def test_load_channel_bad_signature_gets_fixed(self, config_d): # Like above, but the second download of the image signing key results # in a properly signed channels.json file. sign(self._channels_path, 'spare.gpg') setup_keyrings() self._state.run_thru('get_channel') # At this point, the state machine has determined that the # channels.json file is not signed with the cached image signing key, # so it will try to download a new imaging signing key. Let's put one # on the server, but it will not match the key that channels.json is # signed with. self.assertIsNone(self._state.channels) setup_keyring_txz( 'spare.gpg', 'image-master.gpg', dict(type='image-signing'), os.path.join(self._serverdir, 'gpg', 'image-signing.tar.xz')) # This will succeed by grabbing a new image-signing key. config = Configuration(config_d) with open(config.gpg.image_signing, 'rb') as fp: checksum = hashlib.md5(fp.read()).digest() next(self._state) with open(config.gpg.image_signing, 'rb') as fp: self.assertNotEqual(checksum, hashlib.md5(fp.read()).digest()) # The next state transition will find that the channels.json file is # properly signed. next(self._state) self.assertIsNotNone(self._state.channels) self.assertEqual( self._state.channels.daily.devices.nexus7.keyring.signature, '/daily/nexus7/device-keyring.tar.xz.asc')
def test_asc_file_missing(self): # If the tar.xz.asc file cannot be downloaded, an error is raised. tarxz_path = os.path.join(self._serverdir, 'gpg', 'blacklist.tar.xz') setup_keyrings() setup_keyring_txz('spare.gpg', 'archive-master.gpg', dict(type='blacklist'), tarxz_path) os.remove(tarxz_path + '.asc') self.assertRaises(FileNotFoundError, get_keyring, 'blacklist', 'gpg/blacklist.tar.xz', 'image-master')
def test_good_path(self): # Everything checks out, with the simplest possible keyring.json. setup_keyrings('archive-master') setup_keyring_txz( 'spare.gpg', 'archive-master.gpg', dict(type='image-master'), os.path.join(self._serverdir, 'gpg', 'image-master.tar.xz')) get_keyring('image-master', 'gpg/image-master.tar.xz', 'archive-master') with Context(config.gpg.archive_master) as ctx: self.assertEqual(ctx.fingerprints, set(['289518ED3A0C4CFE975A0B32E0979A7EADE8E880']))
def test_path_blacklist(self): # Get the blacklist keyring. setup_keyrings('archive-master', 'image-master') setup_keyring_txz( 'spare.gpg', 'image-master.gpg', dict(type='blacklist'), os.path.join(self._serverdir, 'gpg/blacklist.tar.xz')) url = 'gpg/blacklist.tar.xz'.format(config.channel, config.device) get_keyring('blacklist', url, 'image-master') blacklist_path = os.path.join(config.tempdir, 'blacklist.tar.xz') with Context(blacklist_path) as ctx: self.assertEqual(ctx.fingerprints, set(['94BE2CECF8A5AF9F3A10E2A6526B7016C3D2FB44']))
def test_good_validation(self): # The .validate() method does nothing if the signature is good. channels_json = os.path.join(self._tmpdir, 'channels.json') copy('gpg.channels_01.json', self._tmpdir, dst=channels_json) sign(channels_json, 'image-signing.gpg') with temporary_directory() as tmpdir: keyring = os.path.join(tmpdir, 'image-signing.tar.xz') setup_keyring_txz('image-signing.gpg', 'image-master.gpg', dict(type='image-signing'), keyring) with Context(keyring) as ctx: self.assertIsNone( ctx.validate(channels_json + '.asc', channels_json))
def test_bad_json_type(self): # This type, while the signatures match, the keyring type in the # keyring.json file does not match. setup_keyrings() setup_keyring_txz( 'device-signing.gpg', 'image-master.gpg', dict(type='master'), os.path.join(self._serverdir, 'gpg', 'blacklist.tar.xz')) with self.assertRaises(KeyringError) as cm: get_keyring('blacklist', 'gpg/blacklist.tar.xz', 'image-master') self.assertEqual( cm.exception.message, 'keyring type mismatch; wanted: blacklist, got: master')
def test_bad_json_model(self): # Similar to above, but with a non-matching model name. setup_keyrings() setup_keyring_txz( 'device-signing.gpg', 'image-master.gpg', dict(type='blacklist', model='nexus0'), os.path.join(self._serverdir, 'gpg', 'blacklist.tar.xz')) with self.assertRaises(KeyringError) as cm: get_keyring('blacklist', 'gpg/blacklist.tar.xz', 'image-master') self.assertEqual( cm.exception.message, 'keyring model mismatch; wanted: nexus7, got: nexus0')
def test_good_path_model(self): # Everything checks out with the model specified. setup_keyrings() setup_keyring_txz( 'spare.gpg', 'archive-master.gpg', dict(type='image-master', model='nexus7'), os.path.join(self._serverdir, 'gpg', 'image-master.tar.xz')) get_keyring('image-master', 'gpg/image-master.tar.xz', 'archive-master') with Context(config.gpg.archive_master) as ctx: self.assertEqual(ctx.fingerprints, set(['289518ED3A0C4CFE975A0B32E0979A7EADE8E880']))
def test_bad_not_even_a_signature(self): # The signature file isn't even a signature file. channels_json = os.path.join(self._tmpdir, 'channels.json') copy('gpg.channels_01.json', self._tmpdir, dst=channels_json) copy('gpg.channels_01.json', self._tmpdir, dst=channels_json + '.asc') with temporary_directory() as tmpdir: dst = os.path.join(tmpdir, 'device-signing.tar.xz') setup_keyring_txz('device-signing.gpg', 'image-signing.gpg', dict(type='device-signing'), dst) with Context(dst) as ctx: self.assertFalse(ctx.verify( channels_json + '.asc', channels_json))
def test_good_path_expiry(self): # Everything checks out, with the expiration date specified. next_year = datetime.now(tz=timezone.utc) + timedelta(days=365) setup_keyrings('archive-master') setup_keyring_txz( 'spare.gpg', 'archive-master.gpg', dict(type='image-master', expiry=next_year.timestamp()), os.path.join(self._serverdir, 'gpg', 'image-master.tar.xz')) get_keyring('image-master', 'gpg/image-master.tar.xz', 'archive-master') with Context(config.gpg.archive_master) as ctx: self.assertEqual(ctx.fingerprints, set(['289518ED3A0C4CFE975A0B32E0979A7EADE8E880']))
def test_path_device_signing_keyring(self): # Get the device signing keyring. setup_keyrings('archive-master', 'image-master', 'image-signing') setup_keyring_txz( 'spare.gpg', 'image-signing.gpg', dict(type='device-signing'), os.path.join(self._serverdir, 'gpg', 'stable', 'nexus7', 'device-signing.tar.xz')) url = 'gpg/{}/{}/device-signing.tar.xz'.format(config.channel, config.device) get_keyring('device-signing', url, 'image-signing') with Context(config.gpg.device_signing) as ctx: self.assertEqual(ctx.fingerprints, set(['94BE2CECF8A5AF9F3A10E2A6526B7016C3D2FB44']))
def test_expired(self): # Similar to above, but the expiry key in the json names a utc # timestamp that has already elapsed. last_year = datetime.now(tz=timezone.utc) + timedelta(days=-365) setup_keyrings() setup_keyring_txz( 'device-signing.gpg', 'image-master.gpg', dict(type='blacklist', model='nexus7', expiry=last_year.timestamp()), os.path.join(self._serverdir, 'gpg', 'blacklist.tar.xz')) with self.assertRaises(KeyringError) as cm: get_keyring('blacklist', 'gpg/blacklist.tar.xz', 'image-master') self.assertEqual(cm.exception.message, 'expired keyring timestamp')
def test_bad_signature(self): # In this case, the file is signed with the device key, so it will not # verify against the image signing key. channels_json = os.path.join(self._tmpdir, 'channels.json') copy('gpg.channels_01.json', self._tmpdir, dst=channels_json) sign(channels_json, 'device-signing.gpg') # Verify the signature with the pubkey. with temporary_directory() as tmpdir: dst = os.path.join(tmpdir, 'image-signing.tar.xz') setup_keyring_txz('image-signing.gpg', 'image-master.gpg', dict(type='image-signing'), dst) with Context(dst) as ctx: self.assertFalse( ctx.verify(channels_json + '.asc', channels_json))
def test_good_signature(self): # We have a channels.json file signed with the imaging signing key, as # would be the case in production. The signature will match a context # loaded with the public key. channels_json = os.path.join(self._tmpdir, 'channels.json') copy('gpg.channels_01.json', self._tmpdir, dst=channels_json) sign(channels_json, 'image-signing.gpg') with temporary_directory() as tmpdir: keyring = os.path.join(tmpdir, 'image-signing.tar.xz') setup_keyring_txz('image-signing.gpg', 'image-master.gpg', dict(type='image-signing'), keyring) with Context(keyring) as ctx: self.assertTrue( ctx.verify(channels_json + '.asc', channels_json))
def test_load_index_with_bad_keyring(self): # Here, the index.json file is signed with a defective device keyring. self._copysign('index.channels_02.json', 'channels.json', 'image-signing.gpg') # This will be signed by a keyring that is not the device keyring. self._copysign('index.index_04.json', 'stable/nexus7/index.json', 'spare.gpg') setup_keyrings() setup_keyring_txz( 'device-signing.gpg', 'image-signing.gpg', dict(type='device-signing'), os.path.join(self._serverdir, 'stable', 'nexus7', 'device.tar.xz')) state = State() state.run_until('get_index') self.assertRaises(SignatureError, next, state)
def test_good_signature_with_multiple_keyrings(self): # Like above, the file is signed with the device key, but this time we # include both the image signing and device signing pubkeys. channels_json = os.path.join(self._tmpdir, 'channels.json') copy('gpg.channels_01.json', self._tmpdir, dst=channels_json) sign(channels_json, 'device-signing.gpg') with temporary_directory() as tmpdir: keyring_1 = os.path.join(tmpdir, 'image-signing.tar.xz') keyring_2 = os.path.join(tmpdir, 'device-signing.tar.xz') setup_keyring_txz('image-signing.gpg', 'image-master.gpg', dict(type='image-signing'), keyring_1) setup_keyring_txz('device-signing.gpg', 'image-signing.gpg', dict(type='device-signing'), keyring_2) with Context(keyring_1, keyring_2) as ctx: self.assertTrue( ctx.verify(channels_json + '.asc', channels_json))
def test_bad_not_even_a_signature(self, config): # The signature file isn't even a signature file. Verification will # fail unless the --skip-gpg-verification flag is set. channels_json = os.path.join(self._tmpdir, 'channels.json') channels_asc = channels_json + '.asc' copy('gpg.channels_01.json', self._tmpdir, dst=channels_json) copy('gpg.channels_01.json', self._tmpdir, dst=channels_json + '.asc') with temporary_directory() as tmpdir: dst = os.path.join(tmpdir, 'device-signing.tar.xz') setup_keyring_txz('device-signing.gpg', 'image-signing.gpg', dict(type='device-signing'), dst) with Context(dst) as ctx: self.assertFalse(ctx.verify(channels_asc, channels_json)) config.skip_gpg_verification = True self.assertTrue(ctx.verify(channels_asc, channels_json))
def test_bad_signature_with_multiple_keyrings(self): # The file is signed with the image master key, but it won't verify # against the image signing and device signing pubkeys. channels_json = os.path.join(self._tmpdir, 'channels.json') copy('gpg.channels_01.json', self._tmpdir, dst=channels_json) sign(channels_json, 'image-master.gpg') # Verify the signature with the pubkey. with temporary_directory() as tmpdir: keyring_1 = os.path.join(tmpdir, 'image-signing.tar.xz') keyring_2 = os.path.join(tmpdir, 'device-signing.tar.xz') setup_keyring_txz('image-signing.gpg', 'image-master.gpg', dict(type='image-signing'), keyring_1) setup_keyring_txz('device-signing.gpg', 'image-signing.gpg', dict(type='device-signing'), keyring_2) with Context(keyring_1, keyring_2) as ctx: self.assertFalse( ctx.verify(channels_json + '.asc', channels_json))
def test_destination_blacklist(self): # Like above, but the blacklist files end up in the temporary # directory, since it's never persistent. setup_keyrings('archive-master', 'image-master') setup_keyring_txz( 'spare.gpg', 'image-master.gpg', dict(type='blacklist'), os.path.join(self._serverdir, 'gpg', 'blacklist.tar.xz')) txz_path = os.path.join(config.updater.data_partition, 'blacklist.tar.xz') asc_path = txz_path + '.asc' self.assertFalse(os.path.exists(txz_path)) self.assertFalse(os.path.exists(asc_path)) get_keyring('blacklist', 'gpg/blacklist.tar.xz', 'image-master') self.assertTrue(os.path.exists(txz_path)) self.assertTrue(os.path.exists(asc_path)) with Context(config.gpg.image_master) as ctx: self.assertTrue(ctx.verify(asc_path, txz_path))
def test_bad_signature_with_validate(self, config): # This is similar to the above, except that the .validate() API is # used instead. channels_json = os.path.join(self._tmpdir, 'channels.json') channels_asc = channels_json + '.asc' copy('gpg.channels_01.json', self._tmpdir, dst=channels_json) sign(channels_json, 'device-signing.gpg') # Verify the signature with the pubkey. with temporary_directory() as tmpdir: dst = os.path.join(tmpdir, 'image-signing.tar.xz') setup_keyring_txz('image-signing.gpg', 'image-master.gpg', dict(type='image-signing'), dst) with Context(dst) as ctx: self.assertRaises(SignatureError, ctx.validate, channels_asc, channels_json) config.skip_gpg_verification = True ctx.validate(channels_asc, channels_json)
def test_destination_image_signing(self): # When a keyring is downloaded, we preserve its .tar.xz and # .tar.xz.asc files. setup_keyrings('archive-master', 'image-master') setup_keyring_txz( 'image-signing.gpg', 'image-master.gpg', dict(type='image-signing'), os.path.join(self._serverdir, 'gpg', 'image-signing.tar.xz')) asc_path = config.gpg.image_signing + '.asc' self.assertFalse(os.path.exists(config.gpg.image_signing)) self.assertFalse(os.path.exists(asc_path)) get_keyring('image-signing', 'gpg/image-signing.tar.xz', 'image-master') self.assertTrue(os.path.exists(config.gpg.image_signing)) self.assertTrue(os.path.exists(asc_path)) with Context(config.gpg.image_master) as ctx: self.assertTrue(ctx.verify(asc_path, config.gpg.image_signing))
def test_blacklisted_signature(self): # Normally, the signature would be good, except that the fingerprint # of the device signing key is blacklisted. setup_keyrings('archive-master', 'image-master') blacklist = os.path.join(config.tempdir, 'gpg', 'blacklist.tar.xz') # Blacklist the image-master keyring. setup_keyring_txz('image-master.gpg', 'image-master.gpg', dict(type='blacklist'), blacklist) setup_keyring_txz( 'image-signing.gpg', 'image-master.gpg', dict(type='image-signing'), os.path.join(self._serverdir, 'gpg', 'image-signing.tar.xz')) # Now put an image-signing key on the server and attempt to download # it. Because the image-master is blacklisted, this will fail. self.assertRaises(SignatureError, get_keyring, 'image-signing', 'gpg/image-signing.tar.xz', 'image-master', blacklist)
def test_load_index_with_device_keyring(self): # Here, the index.json file is signed with a device keyring. self._copysign('index.channels_02.json', 'channels.json', 'image-signing.gpg') # index.index_04.json.json path B will win, with no bootme flags. self._copysign('index.index_04.json', 'stable/nexus7/index.json', 'device-signing.gpg') setup_keyrings() setup_keyring_txz( 'device-signing.gpg', 'image-signing.gpg', dict(type='device-signing'), os.path.join(self._serverdir, 'stable', 'nexus7', 'device.tar.xz')) state = State() state.run_thru('get_index') self.assertEqual( state.index.global_.generated_at, datetime(2013, 4, 29, 18, 45, 27, tzinfo=timezone.utc)) self.assertEqual(state.index.images[0].files[1].checksum, 'bcd')
def test_bad_signature(self, config): # In this case, the file is signed with the device key, so it will not # verify against the image signing key, unless the # --skip-gpg-verification flag is set. channels_json = os.path.join(self._tmpdir, 'channels.json') channels_asc = channels_json + '.asc' copy('gpg.channels_01.json', self._tmpdir, dst=channels_json) sign(channels_json, 'device-signing.gpg') # Verify the signature with the pubkey. with temporary_directory() as tmpdir: dst = os.path.join(tmpdir, 'image-signing.tar.xz') setup_keyring_txz('image-signing.gpg', 'image-master.gpg', dict(type='image-signing'), dst) with Context(dst) as ctx: self.assertFalse(ctx.verify(channels_asc, channels_json)) # But with the --skip-gpg-verification flag set, the verify # call returns success. config.skip_gpg_verification = True self.assertTrue(ctx.verify(channels_asc, channels_json))
def test_signature_error_logging(self): # The repr/str of the SignatureError should contain lots of useful # information that will make debugging easier. channels_json = os.path.join(self._tmpdir, 'channels.json') copy('gpg.channels_01.json', self._tmpdir, dst=channels_json) sign(channels_json, 'device-signing.gpg') # Verify the signature with the pubkey. tmpdir = self._stack.enter_context(temporary_directory()) dst = os.path.join(tmpdir, 'image-signing.tar.xz') setup_keyring_txz('image-signing.gpg', 'image-master.gpg', dict(type='image-signing'), dst) output = StringIO() with Context(dst) as ctx: try: ctx.validate(channels_json + '.asc', channels_json) except SignatureError: # For our purposes, log.exception() is essentially a wrapper # around this traceback call. We don't really care about the # full stack trace though. e = sys.exc_info() traceback.print_exception(e[0], e[1], e[2], limit=0, file=output) # 2014-02-12 BAW: Yuck, but I can't get assertRegex() to work properly. for i, line in enumerate(output.getvalue().splitlines()): if i == 0: self.assertEqual(line, 'Traceback (most recent call last):') elif i == 1: self.assertEqual(line, 'systemimage.gpg.SignatureError: ') elif i == 2: self.assertTrue(line.startswith(' sig path :')) elif i == 3: self.assertTrue(line.endswith('/channels.json.asc')) elif i == 4: self.assertEqual( line, ' data path: 715c63fecbf44b62f9fa04a82dfa7d29') elif i == 5: self.assertTrue(line.endswith('/channels.json')) elif i == 6: self.assertTrue(line.startswith(' keyrings :')) elif i == 7: self.assertTrue(line.endswith("/image-signing.tar.xz']")) elif i == 8: self.assertEqual(line, ' blacklist: no blacklist ')
def test_download_winners_signed_by_device_key(self): # Check that all the winning path's files are downloaded, even when # they are signed by the device key instead of the image signing # master. setup_keyrings() # To set up the device signing key, we need to load channels_03.json # and copy the device keyring to the server. copy('winner.channels_02.json', self._serverdir, 'channels.json') sign(os.path.join(self._serverdir, 'channels.json'), 'image-signing.gpg') setup_keyring_txz( 'device-signing.gpg', 'image-signing.gpg', dict(type='device-signing'), os.path.join(self._serverdir, 'stable', 'nexus7', 'device.tar.xz')) # The index.json file and all the downloadable files must now be # signed with the device key. sign(os.path.join(self._serverdir, self._indexpath), 'device-signing.gpg') setup_index('winner.index_02.json', self._serverdir, 'device-signing.gpg') touch_build(100) # Run the state machine until we download the files. state = State() state.run_thru('download_files') # The B path files contain their checksums. def assert_file_contains(filename, contents): path = os.path.join(config.updater.cache_partition, filename) with open(path, encoding='utf-8') as fp: self.assertEqual(fp.read(), contents) assert_file_contains('5.txt', '345') assert_file_contains('6.txt', '456') assert_file_contains('7.txt', '567') # Delta B.1 files. assert_file_contains('8.txt', '678') assert_file_contains('9.txt', '789') assert_file_contains('a.txt', '89a') # Delta B.2 files. assert_file_contains('b.txt', '9ab') assert_file_contains('d.txt', 'fed') assert_file_contains('c.txt', 'edc')
def test_load_channel_blacklisted_signature(self, config_d): # We get an error if the signature on the channels.json file is good # but the key is blacklisted. sign(self._channels_path, 'image-signing.gpg') setup_keyrings() setup_keyring_txz( 'image-signing.gpg', 'image-master.gpg', dict(type='blacklist'), os.path.join(self._serverdir, 'gpg', 'blacklist.tar.xz')) self._state.run_thru('get_channel') # We now have an image-signing key which is blacklisted. This will # cause the state machine to try to download a new image signing key, # so let's put the cached one up on the server. This will still be # backlisted though. config = Configuration(config_d) key_path = os.path.join(self._serverdir, 'gpg', 'image-signing.tar.xz') shutil.copy(config.gpg.image_signing, key_path) shutil.copy(config.gpg.image_signing + '.asc', key_path + '.asc') # Run the state machine through _get_channel() again, only this time # because the key is still blacklisted, we'll get an exception. self.assertRaises(SignatureError, self._state.run_thru, 'get_channel')