/* * Copyright (C) 2017 Sami Vänttinen * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "HostInstaller.h" #include "config-keepassx.h" #include #include #include #include #include #include #include #include HostInstaller::HostInstaller() : HOST_NAME("org.keepassxc.keepassxc_browser") , ALLOWED_EXTENSIONS(QStringList() << "keepassxc-browser@keepassxc.org") , ALLOWED_ORIGINS(QStringList() << "chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/" << "chrome-extension://oboonakemofpalcgghocfoadofidjkkk/") #if defined(Q_OS_MACOS) , TARGET_DIR_CHROME("/Library/Application Support/Google/Chrome/NativeMessagingHosts") , TARGET_DIR_CHROMIUM("/Library/Application Support/Chromium/NativeMessagingHosts") , TARGET_DIR_FIREFOX("/Library/Application Support/Mozilla/NativeMessagingHosts") , TARGET_DIR_VIVALDI("/Library/Application Support/Vivaldi/NativeMessagingHosts") , TARGET_DIR_TOR_BROWSER("/Library/Application Support/TorBrowser-Data/Browser/Mozilla/NativeMessagingHosts") , TARGET_DIR_BRAVE("/Library/Application Support/BraveSoftware/Brave-Browser/NativeMessagingHosts") , TARGET_DIR_EDGE("/Library/Application Support/Microsoft Edge/NativeMessagingHosts") #elif defined(Q_OS_WIN) // clang-format off , TARGET_DIR_CHROME("HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser") , TARGET_DIR_CHROMIUM("HKEY_CURRENT_USER\\Software\\Chromium\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser") // clang-format on , TARGET_DIR_FIREFOX("HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser") , TARGET_DIR_VIVALDI(TARGET_DIR_CHROME) , TARGET_DIR_TOR_BROWSER(TARGET_DIR_FIREFOX) , TARGET_DIR_BRAVE(TARGET_DIR_CHROME) , TARGET_DIR_EDGE( "HKEY_CURRENT_USER\\Software\\Microsoft\\Edge\\NativeMessagingHosts\\org.keepassxc.keepassxc_browser") #else , TARGET_DIR_CHROME("/.config/google-chrome/NativeMessagingHosts") , TARGET_DIR_CHROMIUM("/.config/chromium/NativeMessagingHosts") , TARGET_DIR_FIREFOX("/.mozilla/native-messaging-hosts") , TARGET_DIR_VIVALDI("/.config/vivaldi/NativeMessagingHosts") , TARGET_DIR_TOR_BROWSER("/.tor-browser/app/Browser/TorBrowser/Data/Browser/.mozilla/native-messaging-hosts") , TARGET_DIR_BRAVE("/.config/BraveSoftware/Brave-Browser/NativeMessagingHosts") , TARGET_DIR_EDGE("/.config/microsoftedge/NativeMessagingHosts") #endif { } /** * Checks if the selected browser has native messaging host properly installed * * @param browser Selected browser * @return bool Script is installed correctly */ bool HostInstaller::checkIfInstalled(SupportedBrowsers browser) { QString fileName = getPath(browser); #ifdef Q_OS_WIN QSettings settings(getTargetPath(browser), QSettings::NativeFormat); return registryEntryFound(settings); #else return QFile::exists(fileName); #endif } /** * Checks if keepassxc-proxy location is found * * @param proxy Is keepassxc-proxy enabled * @param location Custom proxy location * @param path The path is set here and returned to the caller * @return bool */ bool HostInstaller::checkIfProxyExists(const bool& proxy, const QString& location, QString& path) const { QString fileName = getProxyPath(proxy, location); path = fileName; return QFile::exists(fileName); } /** * Installs native messaging JSON script for the selected browser * * @param browser Selected browser * @param enabled Is browser integration enabled * @param proxy Is keepassxc-proxy enabled * @param location Custom proxy location */ void HostInstaller::installBrowser(SupportedBrowsers browser, const bool& enabled, const bool& proxy, const QString& location) { if (enabled) { #ifdef Q_OS_WIN // Create a registry key QSettings settings(getTargetPath(browser), QSettings::NativeFormat); settings.setValue("Default", getPath(browser)); #endif // Always create the script file QJsonObject script = constructFile(browser, proxy, location); if (!saveFile(browser, script)) { QMessageBox::critical(nullptr, tr("KeePassXC: Cannot save file!"), tr("Cannot save the native messaging script file."), QMessageBox::Ok); } } else { // Remove the script file QString fileName = getPath(browser); QFile::remove(fileName); #ifdef Q_OS_WIN // Remove the registry entry QSettings settings(getTargetPath(browser), QSettings::NativeFormat); settings.remove("Default"); #endif } } /** * Updates the paths to native messaging host for each browser that has been enabled * * @param proxy Is keepassxc-proxy enabled * @param location Custom proxy location */ void HostInstaller::updateBinaryPaths(const bool& proxy, const QString& location) { for (int i = 0; i <= SupportedBrowsers::EDGE; ++i) { if (checkIfInstalled(static_cast(i))) { installBrowser(static_cast(i), true, proxy, location); } } } /** * Returns the target path for each browser. Windows uses a registry path instead of a file path * * @param browser Selected browser * @return QString Current target path for the selected browser */ QString HostInstaller::getTargetPath(SupportedBrowsers browser) const { switch (browser) { case SupportedBrowsers::CHROME: return TARGET_DIR_CHROME; case SupportedBrowsers::CHROMIUM: return TARGET_DIR_CHROMIUM; case SupportedBrowsers::FIREFOX: return TARGET_DIR_FIREFOX; case SupportedBrowsers::VIVALDI: return TARGET_DIR_VIVALDI; case SupportedBrowsers::TOR_BROWSER: return TARGET_DIR_TOR_BROWSER; case SupportedBrowsers::BRAVE: return TARGET_DIR_BRAVE; case SupportedBrowsers::EDGE: return TARGET_DIR_EDGE; default: return QString(); } } /** * Returns the browser name * Needed for Windows to separate Chromium- or Firefox-based scripts * * @param browser Selected browser * @return QString Name of the selected browser */ QString HostInstaller::getBrowserName(SupportedBrowsers browser) const { switch (browser) { case SupportedBrowsers::CHROME: return "chrome"; case SupportedBrowsers::CHROMIUM: return "chromium"; case SupportedBrowsers::FIREFOX: return "firefox"; case SupportedBrowsers::VIVALDI: return "vivaldi"; case SupportedBrowsers::TOR_BROWSER: return "tor-browser"; case SupportedBrowsers::BRAVE: return "brave"; case SupportedBrowsers::EDGE: return "edge"; default: return QString(); } } /** * Returns the path of native messaging JSON script for the selected browser * * @param browser Selected browser * @return QString JSON script path for the selected browser */ QString HostInstaller::getPath(SupportedBrowsers browser) const { #ifdef Q_OS_WIN // If portable settings file exists save the JSON scripts to application folder QString userPath; QString portablePath = QCoreApplication::applicationDirPath() + "/keepassxc.ini"; if (QFile::exists(portablePath)) { userPath = QCoreApplication::applicationDirPath(); } else { userPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); } QString winPath = QString("%1/%2_%3.json").arg(userPath, HOST_NAME, getBrowserName(browser)); winPath.replace("/", "\\"); return winPath; #else QString path = getTargetPath(browser); return QString("%1%2/%3.json").arg(QDir::homePath(), path, HOST_NAME); #endif } /** * Gets the installation directory for JSON script file (application install path) * * @param browser Selected browser * @return QString Install path */ QString HostInstaller::getInstallDir(SupportedBrowsers browser) const { QString path = getTargetPath(browser); #ifdef Q_OS_WIN return QCoreApplication::applicationDirPath(); #else return QString("%1%2").arg(QDir::homePath(), path); #endif } /** * Gets the path to keepassxc-proxy binary * * @param proxy Is keepassxc-proxy used with KeePassXC * @param location Custom proxy path * @return path Path to keepassxc-proxy */ QString HostInstaller::getProxyPath(const bool& proxy, const QString& location) const { QString path; #ifdef KEEPASSXC_DIST_APPIMAGE if (proxy && !location.isEmpty()) { path = location; } else { path = QProcessEnvironment::systemEnvironment().value("APPIMAGE"); } #else if (proxy) { if (!location.isEmpty()) { path = location; } else { path = QFileInfo(QCoreApplication::applicationFilePath()).absolutePath(); path.append("/keepassxc-proxy"); #ifdef Q_OS_WIN path.append(".exe"); #endif } } else { path = QFileInfo(QCoreApplication::applicationFilePath()).absoluteFilePath(); } #ifdef Q_OS_WIN path.replace("/", "\\"); #endif #endif // #ifdef KEEPASSXC_DIST_APPIMAGE return path; } /** * Constructs the JSON script file used with native messaging * * @param browser Browser (Chromium- and Firefox-based browsers need a different parameters for the script) * @param proxy Is keepassxc-proxy used with KeePassXC * @param location Custom proxy location * @return script The JSON script file */ QJsonObject HostInstaller::constructFile(SupportedBrowsers browser, const bool& proxy, const QString& location) { QString path = getProxyPath(proxy, location); QJsonObject script; script["name"] = HOST_NAME; script["description"] = QString("KeePassXC integration with native messaging support"); script["path"] = path; script["type"] = QString("stdio"); QJsonArray arr; if (browser == SupportedBrowsers::FIREFOX || browser == SupportedBrowsers::TOR_BROWSER) { for (const QString& extension : ALLOWED_EXTENSIONS) { arr.append(extension); } script["allowed_extensions"] = arr; } else { for (const QString& origin : ALLOWED_ORIGINS) { arr.append(origin); } script["allowed_origins"] = arr; } return script; } /** * Checks if a registry setting is found with default value * * @param settings Registry path * @return bool Is the registry value found */ bool HostInstaller::registryEntryFound(const QSettings& settings) { return !settings.value("Default").isNull(); } /** * Saves a JSON script file * * @param browser Selected browser * @param script JSON native messaging script object * @return bool Write succeeds */ bool HostInstaller::saveFile(SupportedBrowsers browser, const QJsonObject& script) { QString path = getPath(browser); QString installDir = getInstallDir(browser); QDir dir(installDir); if (!dir.exists()) { QDir().mkpath(installDir); } QFile scriptFile(path); if (!scriptFile.open(QIODevice::WriteOnly)) { return false; } QJsonDocument doc(script); return scriptFile.write(doc.toJson()) >= 0; }