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
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
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