Example #1
0
def signFolder(private_key_path: str, path: str, ignore_folders: List[str],
               optional_password: Optional[str]) -> bool:
    """Generate a signature for a folder (given a private key) and save it to a json signature file within that folder.

    - The signature file itself will be signed, this is saved in the 'root_manifest_signature'.
      This happens in a slightly different way in comparison to the other files:
      The key in this case is the hash of the alphabetized*, concatenated** name/hash pairs.
      (Not the entire file: as the self-sign is included in the manifest, this would cause an infinite recursion).
      *)  Where the name is the key as it appears in 'root-signatures', so 'src/b.py' will go _after_ 'c', because c < s
      **) For all keys in 'root-signatures': key0/hash0/key1/hash1/key2/hash2/... (without the slashes!)
    - On validation, any 'extra' files not in the ignored (cache) folders should also trigger failure.
    - Be careful, check if symlinks have been followed after signing!

    A json signature file for a folder looks like this:
    {
      "root_manifest_signature": "...<key in base-64>...",
      "root_signatures": {
        "text.txt": "...<key in base-64>...",
        "img.png": "...<key in base-64>...",
        "subfolder/text.txt": "...<key in base-64>..."
      }
    }

    Note that, within the file, the order of keys is not guaranteed: 'root_manifest_signature' may appear before or
    after 'root_signatures', and the order of keys within 'root_signatures' can be anything as long as the right hashes
    are associated with the right keys.

    See also 'Trust.py' in the main library and the related scripts; 'createkeypair.py', 'signfile.py' in this folder.

    :param private_key_path: Path to the file containing the private key.
    :param path: The folder to be signed.
    :param ignore_folders: Local cache folders (that should be deleted on restart of the app) can be ignored.
    :param optional_password: If the private key has a password, it should be provided here.
    :return: Whether a valid signature file has been generated and saved.
    """

    path = path if path not in ["", "."] else os.getcwd()
    password = None if optional_password == "" else optional_password
    private_key = TrustBasics.loadPrivateKey(private_key_path, password)
    if private_key is None:
        return False

    try:
        signatures = {}  # Dict[str, str]

        # Loop over all files in the folder:
        for root, dirnames, filenames in os.walk(path, followlinks=True):
            if os.path.basename(root) in ignore_folders:
                continue
            for filename in filenames:
                if filename == TrustBasics.getSignaturesLocalFilename(
                ) and root == path:
                    continue

                # Generate a signature for the current file:
                name_on_disk, name_in_data = TrustBasics.getFilePathInfo(
                    path, root, filename)
                signature = TrustBasics.getFileSignature(
                    name_on_disk, private_key)
                if signature is None:
                    Logger.logException(
                        "e", "Couldn't sign file '{0}'.".format(name_on_disk))
                    return False
                signatures[name_in_data] = signature

        # Make the self-signature for the whole 'self-signed' aspect of the manifest:
        self_signature = TrustBasics.getHashSignature(
            TrustBasics.getSelfSignHash(signatures), private_key)

        # Save signatures to json:
        wrapped_signatures = {
            TrustBasics.getRootSignatureCategory(): signatures,
            TrustBasics.getRootSignedManifestKey(): self_signature
        }

        json_filename = os.path.join(path,
                                     TrustBasics.getSignaturesLocalFilename())
        with open(json_filename, "w", encoding="utf-8") as data_file:
            json.dump(wrapped_signatures, data_file, indent=2)

        Logger.log("i", "Signed folder '{0}'.".format(path))
        return True

    except:  # Yes, we  do really want this on _every_ exception that might occur.
        Logger.logException("e", "Couldn't sign folder '{0}'.".format(path))
    return False
Example #2
0
    def _findPlugin(self, plugin_id: str) -> Optional[types.ModuleType]:
        """Try to find a module implementing a plugin

        :param plugin_id: The name of the plugin to find
        :returns: module if it was found (and, if 'self._check_if_trusted' is set, also secure), None otherwise
        """

        if plugin_id in self._found_plugins:
            return self._found_plugins[plugin_id]
        locations = []
        for folder in self._plugin_locations:
            location = self._locatePlugin(plugin_id, folder)
            if location:
                locations.append(location)

        if not locations:
            return None
        final_location = locations[0]

        if len(locations) > 1:
            # We found multiple versions of the plugin. Let's find out which one to load!
            highest_version = Version(0)

            for loc in locations:
                meta_data = {}  # type: Dict[str, Any]
                plugin_location = os.path.join(loc, plugin_id)
                metadata_file = os.path.join(plugin_location, "plugin.json")
                try:
                    with open(metadata_file, "r",
                              encoding="utf-8") as file_stream:
                        self._parsePluginInfo(plugin_id, file_stream.read(),
                                              meta_data)
                except:
                    pass
                current_version = Version(meta_data["plugin"]["version"])
                if current_version > highest_version:
                    highest_version = current_version
                    final_location = loc
        try:
            file, path, desc = imp.find_module(plugin_id, [final_location])
        except Exception:
            Logger.logException("e", "Import error when importing %s",
                                plugin_id)
            return None

        # Define a trusted plugin as either: already checked, correctly signed, or bundled with the application.
        if self._check_if_trusted and plugin_id not in self._checked_plugin_ids and not self.isBundledPlugin(
                plugin_id):

            # NOTE: '__pychache__'s (+ subfolders) are deleted on startup _before_ load module:
            if not TrustBasics.removeCached(path):
                self._distrusted_plugin_ids.append(plugin_id)
                return None

            # Do the actual check:
            if self._trust_checker is not None and self._trust_checker.signedFolderCheck(
                    path):
                self._checked_plugin_ids.append(plugin_id)
            else:
                self._distrusted_plugin_ids.append(plugin_id)
                return None

        try:
            module = imp.load_module(
                plugin_id, file, path, desc
            )  #type: ignore #MyPy gets the wrong output type from imp.find_module for some reason.
        except Exception:
            Logger.logException("e", "Import error loading module %s",
                                plugin_id)
            return None
        finally:
            if file:
                os.close(
                    file
                )  #type: ignore #MyPy gets the wrong output type from imp.find_module for some reason.
        self._found_plugins[plugin_id] = module
        return module
Example #3
0
def signFolder(private_key_path: str, path: str, ignore_folders: List[str],
               optional_password: Optional[str]) -> bool:
    """Generate a signature for a folder (given a private key) and save it to a json signature file within that folder.

    - The signature file itself will not be signed.
    - On validation, any 'extra' files not in the ignored (cache) folders should also trigger failure.
    - Be careful, symlinks will be followed!

    A json signature file for a folder looks like this:
    {
      "root_signatures": {
        "text.txt": "...<key in base-64>...",
        "img.png": "...<key in base-64>...",
        "subfolder/text.txt": "...<key in base-64>..."
      }
    }

    :param private_key_path: Path to the file containing the private key.
    :param path: The folder to be signed.
    :param ignore_folders: Local cache folders (that should be deleted on restart of the app) can be ignored.
    :param optional_password: If the private key has a password, it should be provided here.
    :return: Whether a valid signature file has been generated and saved.
    """

    password = None if optional_password == "" else optional_password
    private_key = TrustBasics.loadPrivateKey(private_key_path, password)
    if private_key is None:
        return False

    try:
        signatures = {}  # Dict[str, str]

        # Loop over all files in the folder:
        for root, dirnames, filenames in os.walk(path, followlinks=True):
            if os.path.basename(root) in ignore_folders:
                continue
            for filename in filenames:
                if filename == TrustBasics.getSignaturesLocalFilename(
                ) and root == path:
                    continue

                # Generate a signature for the current file:
                name_on_disk, name_in_data = TrustBasics.getFilePathInfo(
                    path, root, filename)
                signature = TrustBasics.getFileSignature(
                    name_on_disk, private_key)
                if signature is None:
                    Logger.logException(
                        "e", "Couldn't sign file '{0}'.".format(name_on_disk))
                    return False
                signatures[name_in_data] = signature

        # Save signatures to json:
        wrapped_signatures = {
            TrustBasics.getRootSignatureCategory(): signatures
        }

        json_filename = os.path.join(path,
                                     TrustBasics.getSignaturesLocalFilename())
        with open(json_filename, "w", encoding="utf-8") as data_file:
            json.dump(wrapped_signatures, data_file, indent=2)

        Logger.log("i", "Signed folder '{0}'.".format(path))
        return True

    except:  # Yes, we  do really want this on _every_ exception that might occur.
        Logger.logException("e", "Couldn't sign folder '{0}'.".format(path))
    return False