示例#1
0
 def __init__(self, logfile=None, loglevel='info'):
     self.loglevel = loglevel
     # Non-public.
     self._stack = ExitStack()
     self._stoppers = []
     # Public.
     self.tmpdir = self._stack.enter_context(temporary_directory())
     self.config_path = os.path.join(self.tmpdir, 'dbus-system.conf')
     self.serverdir = self._stack.enter_context(temporary_directory())
     self.daemon_pid = None
     self.mode = 'live'
     self.udm_certs = ''
     self.curl_cert = ''
     self.patcher = None
     # Set up the dbus-daemon system configuration file.
     path = data_path('dbus-system.conf.in')
     with open(path, 'r', encoding='utf-8') as fp:
         template = fp.read()
     username = pwd.getpwuid(os.getuid()).pw_name
     config = template.format(tmpdir=self.tmpdir, user=username)
     with open(self.config_path, 'w', encoding='utf-8') as fp:
         fp.write(config)
     # We need a client.ini file for the subprocess.
     self.ini_tmpdir = self._stack.enter_context(temporary_directory())
     self.ini_vardir = self._stack.enter_context(temporary_directory())
     self.ini_logfile = (os.path.join(self.ini_tmpdir, 'client.log')
                         if logfile is None
                         else logfile)
     self.ini_path = os.path.join(self.tmpdir, 'config.d')
     makedirs(self.ini_path)
     self._reset_configs()
示例#2
0
 def test_date_unknown(self):
     # If there is no /userdata/.last_update file and no ini files, then
     # the last update date is unknown.
     with ExitStack() as stack:
         config_d = stack.enter_context(temporary_directory())
         tempdir = stack.enter_context(temporary_directory())
         userdata_path = os.path.join(tempdir, '.last_update')
         stack.enter_context(
             patch('systemimage.helpers.LAST_UPDATE_FILE', userdata_path))
         config = Configuration(config_d)
         stack.enter_context(patch('systemimage.config._config', config))
         self.assertEqual(last_update_date(), 'Unknown')
示例#3
0
 def setUp(self):
     # Start both an HTTP and an HTTPS server running.  The former is for
     # the zip files and the latter is for everything else.  Vend them out
     # of a temporary directory which we load up with the right files.
     self._stack = ExitStack()
     try:
         self._serverdir = self._stack.enter_context(temporary_directory())
         copy('winner.channels_01.json', self._serverdir, 'channels.json')
         sign(os.path.join(self._serverdir, 'channels.json'),
              'image-signing.gpg')
         # Path B will win, with no bootme flags.
         self._indexpath = os.path.join('stable', 'nexus7', 'index.json')
         copy('winner.index_02.json', self._serverdir, self._indexpath)
         sign(os.path.join(self._serverdir, self._indexpath),
              'image-signing.gpg')
         # Create every file in path B.  The file contents will be the
         # checksum value.  We need to create the signatures on the fly.
         setup_index('winner.index_02.json', self._serverdir,
                     'image-signing.gpg')
         self._stack.push(
             make_http_server(self._serverdir, 8943, 'cert.pem', 'key.pem'))
         self._stack.push(make_http_server(self._serverdir, 8980))
     except:
         self._stack.close()
         raise
示例#4
0
 def test_dangling_symlink(self, config):
     # LP: #1495688 reports a problem where /userdata/.last_update doesn't
     # exist, and the files in the config.d directory are dangling
     # symlinks.  In this case, there's really little that can be done to
     # find a reliable last update date, but at least we don't crash.
     #
     # Start by deleting any existing .ini files in config.d.
     for path in Path(config.config_d).iterdir():
         if path.suffix == '.ini':
             path.unlink()
     with ExitStack() as stack:
         tmpdir = stack.enter_context(temporary_directory())
         userdata_path = Path(tmpdir) / '.last_update'
         stack.enter_context(
             patch('systemimage.helpers.LAST_UPDATE_FILE',
                   str(userdata_path)))
         # Do not create the .last_update file.
         missing_ini = Path(tmpdir) / 'missing.ini'
         config.ini_files = [missing_ini]
         # Do not create the missing.ini file, but do create a symlink from
         # a config.d file to this missing file.
         default_ini = Path(config.config_d) / '00_default.ini'
         default_ini.symlink_to(missing_ini)
         last_update_date()
         self.assertEqual(last_update_date(), 'Unknown')
示例#5
0
def sign(filename, pubkey_ring):
    """GPG sign the given file, producing an armored detached signature.

    :param filename: The path to the file to sign.
    :param pubkey_ring: The public keyring containing the key to sign the file
        with.  This keyring must contain only one key, and its key id must
        exist in the master secret keyring.
    """
    # filename could be a Path object.  For now, just str-ify it.
    filename = str(filename)
    with ExitStack() as resources:
        home = resources.enter_context(temporary_directory())
        secring = data_path('master-secring.gpg')
        pubring = data_path(pubkey_ring)
        ctx = gnupg.GPG(
            gnupghome=home,
            keyring=pubring,
            #verbose=True,
            secret_keyring=secring)
        public_keys = ctx.list_keys()
        assert len(public_keys) != 0, 'No keys found'
        assert len(public_keys) == 1, 'Too many keys'
        key_id = public_keys[0]['keyid']
        dfp = resources.enter_context(open(filename, 'rb'))
        signed_data = ctx.sign_file(dfp, keyid=key_id, detach=True)
        sfp = resources.enter_context(open(filename + '.asc', 'wb'))
        sfp.write(signed_data.data)
示例#6
0
 def tempdir(self):
     if self._tempdir is None:
         makedirs(self.system.tempdir)
         self._tempdir = self._resources.enter_context(
             temporary_directory(prefix='system-image-',
                                 dir=self.system.tempdir))
     return self._tempdir
示例#7
0
 def setUp(self):
     # Avoid circular imports.
     from systemimage.state import State
     self._resources = ExitStack()
     self._state = State()
     try:
         self._serverdir = self._resources.enter_context(
             temporary_directory())
         # Start up both an HTTPS and HTTP server.  The data files are
         # vended over the latter, everything else, over the former.
         self._resources.push(
             make_http_server(self._serverdir, 8943, 'cert.pem', 'key.pem'))
         self._resources.push(make_http_server(self._serverdir, 8980))
         # Set up the server files.
         assert self.CHANNEL_FILE is not None, (
             'Subclasses must set CHANNEL_FILE')
         copy(self.CHANNEL_FILE, self._serverdir, 'channels.json')
         sign(os.path.join(self._serverdir, 'channels.json'),
              'image-signing.gpg')
         assert self.CHANNEL is not None, 'Subclasses must set CHANNEL'
         assert self.DEVICE is not None, 'Subclasses must set DEVICE'
         index_path = os.path.join(self._serverdir, self.CHANNEL,
                                   self.DEVICE, 'index.json')
         head, tail = os.path.split(index_path)
         assert self.INDEX_FILE is not None, (
             'Subclasses must set INDEX_FILE')
         copy(self.INDEX_FILE, head, tail)
         sign(index_path, self.SIGNING_KEY)
         setup_index(self.INDEX_FILE, self._serverdir, self.SIGNING_KEY)
     except:
         self._resources.close()
         raise
     self.addCleanup(self._resources.close)
示例#8
0
def setup_keyring_txz(keyring_src, signing_keyring, json_data, dst):
    """Set up the <keyring>.tar.xz and .asc files.

    The source keyring and json data is used to create a .tar.xz file
    and an associated .asc signature file.  These are then copied to the
    given destination path name.

    :param keyring_src: The name of the source keyring (i.e. .gpg file), which
        should be relative to the test data directory.  This will serve as the
        keyring.gpg file inside the tarball.
    :param signing_keyring: The name of the keyring to sign the resulting
        tarball with, again, relative to the test data directory.
    :param json_data: The JSON data dictionary, i.e. the contents of the
        keyring.json file inside the tarball.
    :param dst: The destination path of the .tar.xz file.  For the resulting
        signature file, the .asc suffix will be automatically appended and
        copied next to the dst file.
    """
    with temporary_directory() as tmpdir:
        copy(keyring_src, tmpdir, 'keyring.gpg')
        json_path = os.path.join(tmpdir, 'keyring.json')
        with open(json_path, 'w', encoding='utf-8') as fp:
            json.dump(json_data, fp)
        # Tar up the .gpg and .json files into a .tar.xz file.
        tarxz_path = os.path.join(tmpdir, 'keyring.tar.xz')
        with tarfile.open(tarxz_path, 'w:xz') as tf:
            tf.add(os.path.join(tmpdir, 'keyring.gpg'), 'keyring.gpg')
            tf.add(json_path, 'keyring.json')
        sign(tarxz_path, signing_keyring)
        # Copy the .tar.xz and .asc files to the proper directory under
        # the path the https server is vending them from.
        makedirs(os.path.dirname(dst))
        shutil.copy(tarxz_path, dst)
        shutil.copy(tarxz_path + '.asc', dst + '.asc')
示例#9
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)
示例#10
0
 def setUp(self):
     self._resources = ExitStack()
     tmpdir = self._resources.enter_context(temporary_directory())
     self._mid_path = os.path.join(tmpdir, 'machine-id')
     self._resources.enter_context(
         patch('systemimage.helpers.UNIQUE_MACHINE_ID_FILES',
               [self._mid_path]))
示例#11
0
 def test_cancel(self):
     # Try to cancel the download of a big file.
     self.assertEqual(os.listdir(config.tempdir), [])
     with ExitStack() as stack:
         serverdir = stack.enter_context(temporary_directory())
         stack.push(make_http_server(serverdir, 8980))
         # Create a couple of big files to download.
         write_bytes(os.path.join(serverdir, 'bigfile_1.dat'), 10)
         write_bytes(os.path.join(serverdir, 'bigfile_2.dat'), 10)
         # The download service doesn't provide reliable cancel
         # granularity, so instead, we mock the 'started' signal to
         # immediately cancel the download.
         downloader = get_download_manager()
         def cancel_on_start(self, signal, path, started):
             if started:
                 downloader.cancel()
         stack.enter_context(patch(
             'systemimage.udm.DownloadReactor._do_started',
             cancel_on_start))
         self.assertRaises(
             Canceled, downloader.get_files, _http_pathify([
                 ('bigfile_1.dat', 'bigfile_1.dat'),
                 ('bigfile_2.dat', 'bigfile_2.dat'),
                 ]))
         self.assertEqual(os.listdir(config.tempdir), [])
示例#12
0
 def setUp(self):
     self._stack = ExitStack()
     try:
         self._serverdir = self._stack.enter_context(temporary_directory())
         self._stack.push(
             make_http_server(self._serverdir, 8943, 'cert.pem', 'key.pem'))
     except:
         self._stack.close()
         raise
示例#13
0
 def test_offset_no_file(self):
     with ExitStack() as stack:
         tmpdir = stack.enter_context(temporary_directory())
         timekeep_file = Path(tmpdir) / 'timekeep'
         stack.enter_context(
             patch('systemimage.helpers.TIMEKEEPER_OFFSET_FILE',
                   str(timekeep_file)))
         # Don't create the file
         self.assertEqual(get_android_offset(), 0)
示例#14
0
 def setUp(self):
     super().setUp()
     self._resources = ExitStack()
     try:
         self._serverdir = self._resources.enter_context(
             temporary_directory())
         self._resources.push(make_http_server(self._serverdir, 8980))
     except:
         self._resources.close()
         raise
示例#15
0
 def setUp(self):
     self._stack = ExitStack()
     try:
         self._serverdir = self._stack.enter_context(temporary_directory())
         copy('channel.channels_01.json', self._serverdir, 'channels.json')
         sign(os.path.join(self._serverdir, 'channels.json'),
              'image-signing.gpg')
     except:
         self._stack.close()
         raise
示例#16
0
 def test_offset_garbage_file(self):
     with ExitStack() as stack:
         tmpdir = stack.enter_context(temporary_directory())
         timekeep_file = Path(tmpdir) / 'timekeep'
         stack.enter_context(
             patch('systemimage.helpers.TIMEKEEPER_OFFSET_FILE',
                   str(timekeep_file)))
         timekeep_file.touch()
         with open(str(timekeep_file), 'w') as f:
             f.write("This is definitely not a timestamp")
         self.assertEqual(get_android_offset(), 0)
示例#17
0
 def setUp(self):
     # Start the HTTPS server running.  Vend it out of a temporary
     # directory which we load up with the right files.
     self._stack = ExitStack()
     try:
         self._serverdir = self._stack.enter_context(temporary_directory())
         self._stack.push(
             make_http_server(self._serverdir, 8943, 'cert.pem', 'key.pem'))
     except:
         self._stack.close()
         raise
示例#18
0
 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))
示例#19
0
 def test_offset(self):
     with ExitStack() as stack:
         tmpdir = stack.enter_context(temporary_directory())
         timekeep_file = Path(tmpdir) / 'timekeep'
         stack.enter_context(
             patch('systemimage.helpers.TIMEKEEPER_OFFSET_FILE',
                   str(timekeep_file)))
         timestamp = int(datetime(2012, 11, 10, 9, 8, 7).timestamp())
         timekeep_file.touch()
         with open(str(timekeep_file), 'w') as f:
             f.write(str(timestamp))
         self.assertEqual(get_android_offset(), 1352560087)
示例#20
0
 def test_date_from_userdata(self):
     # The last upgrade data can come from /userdata/.last_update.
     with ExitStack() as stack:
         tmpdir = stack.enter_context(temporary_directory())
         userdata_path = Path(tmpdir) / '.last_update'
         stack.enter_context(
             patch('systemimage.helpers.LAST_UPDATE_FILE',
                   str(userdata_path)))
         timestamp = int(datetime(2012, 11, 10, 9, 8, 7).timestamp())
         userdata_path.touch()
         os.utime(str(userdata_path), (timestamp, timestamp))
         self.assertEqual(last_update_date(), '2012-11-10 09:08:07')
示例#21
0
def _wrapper(self, function, ini_files, *args, **kws):
    start = 0
    # It would be preferable to simply add a device='nexus7' argument, but that
    # causes 'decorator() takes 1 positional argument but 2 were given'
    device = kws.get('device', 'nexus7')
    with ExitStack() as resources:
        # Create the config.d directory and copy all the source ini files to
        # this directory in sequential order, interpolating in the temporary
        # tmp and var directories.
        config_d = resources.enter_context(temporary_directory())
        temp_tmpdir = resources.enter_context(temporary_directory())
        temp_vardir = resources.enter_context(temporary_directory())
        for ini_file in ini_files:
            dst = os.path.join(config_d, '{:02d}_override.ini'.format(start))
            start += 1
            template = resource_bytes('systemimage.tests.data',
                                      ini_file).decode('utf-8')
            with atomic(dst) as fp:
                print(template.format(tmpdir=temp_tmpdir, vardir=temp_vardir),
                      file=fp)
        # Patch the global configuration object so that it can be used
        # directly, which is good enough in most cases.  Also patch the bit of
        # code that detects the device name.
        config = Configuration(config_d)
        resources.enter_context(patch('systemimage.config._config', config))
        resources.enter_context(
            patch('systemimage.device.check_output', return_value=device))
        # Make sure the cache_partition and data_partition exist.
        makedirs(config.updater.cache_partition)
        makedirs(config.updater.data_partition)
        # The method under test is allowed to specify some additional
        # keyword arguments, in order to pass some variables in from the
        # wrapper.
        signature = inspect.signature(function)
        if 'config_d' in signature.parameters:
            kws['config_d'] = config_d
        if 'config' in signature.parameters:
            kws['config'] = config
        # Call the function with the given arguments and return the result.
        return function(self, *args)
示例#22
0
 def setUp(self):
     self._stack = ExitStack()
     self._state = State()
     try:
         self._serverdir = self._stack.enter_context(temporary_directory())
         self._stack.push(
             make_http_server(self._serverdir, 8943, 'cert.pem', 'key.pem'))
         copy('channel.channels_01.json', self._serverdir, 'channels.json')
         self._channels_path = os.path.join(self._serverdir,
                                            'channels.json')
     except:
         self._stack.close()
         raise
示例#23
0
 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))
示例#24
0
 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))
示例#25
0
 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))
示例#26
0
 def __enter__(self):
     try:
         # Use a temporary directory for the $GNUPGHOME, but be sure to
         # arrange for the tempdir to be deleted no matter what.
         home = self._stack.enter_context(
             temporary_directory(prefix='si-gnupghome',
                                 dir=config.tempdir))
         self._ctx = gnupg.GPG(gnupghome=home, keyring=self._keyrings)
         self._stack.callback(setattr, self, '_ctx', None)
     except:              # pragma: no cover
         # Restore all context and re-raise the exception.
         self._stack.close()
         raise
     else:
         return self
示例#27
0
def _use_cached_keyring(txz, asc, signing_key):
    if not _use_cached(txz, asc, (signing_key, )):
        return False
    # Do one additional check: unpack the .tar.xz file, grab the keyring.json
    # and if it has an expiry key, make sure that the keyring has not expired.
    with temporary_directory(dir=config.tempdir) as tmp:
        with tarfile.open(txz, 'r:xz') as tf:
            tf.extractall(tmp)
        json_path = os.path.join(tmp, 'keyring.json')
        with open(json_path, 'r', encoding='utf-8') as fp:
            data = json.load(fp)
    expiry = data.get('expiry')
    timestamp = datetime.now(tz=timezone.utc).timestamp()
    # We can use this keyring if it never expires, or if the expiration date
    # is some time in the future.
    return expiry is None or expiry > timestamp
示例#28
0
 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))
示例#29
0
 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))
示例#30
0
 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)