.gitignore | 1 Makefile.am | 4 common/Util.cpp | 21 ++++ common/Util.hpp | 2 tools/Config.cpp | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++++ wsd/FileServer.cpp | 46 +++++++++- 6 files changed, 298 insertions(+), 5 deletions(-)
New commits: commit 7a4bc5b95a9722a23048a7cd076bd1c4a954a9e9 Author: Pranav Kant <pran...@collabora.co.uk> Date: Thu May 25 01:07:20 2017 +0530 admin-console: Check the password against hashed value in config The new password hash property is called secure_password in the config file. `loolconfig` tool should be used to set the password hash in appropriate format with desired salt length, password length, number of iterations in PBKDF2. To be backward compatible, plain-text password for admin-console in config file is still accepted in case secure_password property is missing from the config file. Change-Id: If229999dac62856e368555c0242c4aa6f8061fba diff --git a/common/Util.cpp b/common/Util.cpp index 803ebfb0..66d917d8 100644 --- a/common/Util.cpp +++ b/common/Util.cpp @@ -111,6 +111,27 @@ namespace Util } } + bool dataFromHexString(const std::string& hexString, std::vector<unsigned char>& data) + { + if (hexString.length() % 2 != 0) + { + return false; + } + + data.clear(); + std::stringstream stream; + unsigned value; + for (unsigned offset = 0; offset < hexString.size(); offset += 2) + { + stream.clear(); + stream << std::hex << hexString.substr(offset, 2); + stream >> value; + data.push_back(static_cast<unsigned char>(value)); + } + + return true; + } + std::string encodeId(const unsigned number, const int padding) { std::ostringstream oss; diff --git a/common/Util.hpp b/common/Util.hpp index d2803a42..8c720774 100644 --- a/common/Util.hpp +++ b/common/Util.hpp @@ -42,6 +42,8 @@ namespace Util std::string getFilename(const size_t length); } + /// Hex to unsigned char + bool dataFromHexString(const std::string& hexString, std::vector<unsigned char>& data); /// Encode an integral ID into a string, with padding support. std::string encodeId(const unsigned number, const int padding = 5); /// Decode an integral ID from a string. diff --git a/tools/Config.cpp b/tools/Config.cpp index 60be997c..1692453d 100644 --- a/tools/Config.cpp +++ b/tools/Config.cpp @@ -216,6 +216,7 @@ int Config::main(const std::vector<std::string>& args) std::cout << "Saving configuration to : " << ConfigFile << " ..." << std::endl; _loolConfig.save(ConfigFile); + std::cout << "Saved" << std::endl; } } diff --git a/wsd/FileServer.cpp b/wsd/FileServer.cpp index 99606959..18a82f5c 100644 --- a/wsd/FileServer.cpp +++ b/wsd/FileServer.cpp @@ -9,6 +9,7 @@ #include "config.h" +#include <iomanip> #include <string> #include <vector> #include <unistd.h> @@ -16,6 +17,8 @@ #include <dirent.h> #include <zlib.h> +#include <openssl/evp.h> + #include <Poco/DateTime.h> #include <Poco/DateTimeFormat.h> #include <Poco/DateTimeFormatter.h> @@ -37,6 +40,7 @@ #include "Auth.hpp" #include "Common.hpp" #include "FileServer.hpp" +#include "Protocol.hpp" #include "LOOLWSD.hpp" #include "Log.hpp" @@ -77,18 +81,52 @@ bool FileServerRequestHandler::isAdminLoggedIn(const HTTPRequest& request, LOG_INF("No existing JWT cookie found"); } + HTTPBasicCredentials credentials(request); + std::string userProvidedPwd = credentials.getPassword(); + // If no cookie found, or is invalid, let admin re-login - const auto user = config.getString("admin_console.username", ""); - const auto pass = config.getString("admin_console.password", ""); + const std::string user = config.getString("admin_console.username", ""); + std::string pass = config.getString("admin_console.password", ""); + if (config.has("admin_console.secure_password")) + { + pass = config.getString("admin_console.secure_password"); + // Extract the salt from the config + std::vector<unsigned char> saltData; + std::vector<std::string> tokens = LOOLProtocol::tokenize(pass, '.'); + if (tokens.size() != 5 || + tokens[0] != "pbkdf2" || + tokens[1] != "sha512" || + !Util::dataFromHexString(tokens[3], saltData)) + { + LOG_ERR("Incorrect format detected for secure_password in config file." + << "Denying access until correctly set." + << "Use loolconfig to configure admin password."); + return false; + } + + unsigned char userProvidedPwdHash[tokens[4].size() / 2]; + PKCS5_PBKDF2_HMAC(userProvidedPwd.c_str(), -1, + saltData.data(), saltData.size(), + std::stoi(tokens[2]), + EVP_sha512(), + sizeof userProvidedPwdHash, userProvidedPwdHash); + + std::stringstream stream; + for (unsigned j = 0; j < sizeof userProvidedPwdHash; ++j) + stream << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(userProvidedPwdHash[j]); + + userProvidedPwd = stream.str(); + pass = tokens[4]; + } + if (user.empty() || pass.empty()) { LOG_ERR("Admin Console credentials missing. Denying access until set."); return false; } - HTTPBasicCredentials credentials(request); if (credentials.getUsername() == user && - credentials.getPassword() == pass) + userProvidedPwd == pass) { const std::string htmlMimeType = "text/html"; // generate and set the cookie commit 9bd89e89c31c6e0864bcfa5f550fdbf2e1cd9a49 Author: Pranav Kant <pran...@collabora.co.uk> Date: Wed May 24 14:22:24 2017 +0530 loolconfig: tool to generate admin password hash with PBKDF2 A normal usage to set the admin password would be like : loolconfig set-admin-password --config-file ./loolwsd.xml Other command line options can also be given, like --pwd-salt-length, --pwd-hash-length, --pwd-iterations. Change-Id: I2a6f8d25e068b53a3f945426f0779c8410b2c8ba diff --git a/.gitignore b/.gitignore index 6eaa7f0f..1af727aa 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ loolmount loolmap looltool loolstress +loolconfig loolforkit-nocaps loadtest unittest diff --git a/Makefile.am b/Makefile.am index 73c26140..bd948929 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,7 +2,7 @@ SUBDIRS = . test loleaflet export ENABLE_DEBUG -bin_PROGRAMS = loolwsd loolforkit loolmap loolmount looltool loolstress +bin_PROGRAMS = loolwsd loolforkit loolmap loolmount looltool loolstress loolconfig dist_bin_SCRIPTS = loolwsd-systemplate-setup @@ -135,6 +135,8 @@ loolstress_SOURCES = tools/Stress.cpp \ common/Log.cpp \ common/Util.cpp +loolconfig_SOURCES = tools/Config.cpp + wsd_headers = wsd/Admin.hpp \ wsd/AdminModel.hpp \ wsd/Auth.hpp \ diff --git a/tools/Config.cpp b/tools/Config.cpp new file mode 100644 index 00000000..60be997c --- /dev/null +++ b/tools/Config.cpp @@ -0,0 +1,228 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "config.h" + +#include <iostream> +#include <iomanip> +#include <termios.h> + +#include <openssl/rand.h> +#include <openssl/evp.h> + +#include <Poco/Exception.h> +#include <Poco/Util/Application.h> +#include <Poco/Util/Option.h> +#include <Poco/Util/OptionSet.h> +#include <Poco/Util/XMLConfiguration.h> + +#include "Util.hpp" + +using Poco::Util::Application; +using Poco::Util::Option; +using Poco::Util::OptionSet; +using Poco::Util::XMLConfiguration; + +#define MIN_PWD_SALT_LENGTH 20 +#define MIN_PWD_ITERATIONS 1000 +#define MIN_PWD_HASH_LENGTH 20 + +class LoolConfig final: public XMLConfiguration +{ +public: + LoolConfig() : + XMLConfiguration() + {} +}; + +struct AdminConfig +{ + unsigned pwdSaltLength = 128; + unsigned pwdIterations = 10000; + unsigned pwdHashLength = 128; +}; + +// Config tool to change loolwsd configuration (loolwsd.xml) +class Config: public Application +{ + // Display help information on the console + void displayHelp(); + + LoolConfig _loolConfig; + + AdminConfig _adminConfig; + +public: + static std::string ConfigFile; + +protected: + void defineOptions(OptionSet&) override; + void handleOption(const std::string&, const std::string&) override; + int main(const std::vector<std::string>&) override; +}; + +std::string Config::ConfigFile = LOOLWSD_CONFIGDIR "/loolwsd.xml"; + +void Config::displayHelp() +{ + std::cout << "loolconfig - Configuration tool for LibreOffice Online." << std::endl + << "Commands:" << std::endl + << " set-admin-password" << std::endl; +} + +void Config::defineOptions(OptionSet& optionSet) +{ + Application::defineOptions(optionSet); + + optionSet.addOption(Option("help", "", "Config helper tool to set loolwsd configuration") + .required(false) + .repeatable(false)); + optionSet.addOption(Option("pwd-salt-length", "", "Length of the salt to use to hash password") + .required(false) + .repeatable(false). + argument("number")); + optionSet.addOption(Option("pwd-iterations", "", "Number of iterations to do in PKDBF2 password hashing") + .required(false) + .repeatable(false) + .argument("number")); + optionSet.addOption(Option("pwd-hash-length", "", "Length of password hash to generate") + .required(false) + .repeatable(false) + .argument("number")); + optionSet.addOption(Option("config-file", "", "Specify configuration file path manually.") + .required(false) + .repeatable(false) + .argument("path")); +} + +void Config::handleOption(const std::string& optionName, const std::string& optionValue) +{ + Application::handleOption(optionName, optionValue); + if (optionName == "help") + { + displayHelp(); + std::exit(Application::EXIT_OK); + } + else if (optionName == "config-file") + { + ConfigFile = optionValue; + } + else if (optionName == "pwd-salt-length") + { + unsigned len = std::stoi(optionValue); + if (len < MIN_PWD_SALT_LENGTH) + { + len = MIN_PWD_SALT_LENGTH; + std::cout << "Password salt length adjusted to minimum " << len << std::endl; + } + _adminConfig.pwdSaltLength = len; + } + else if (optionName == "pwd-iterations") + { + unsigned len = std::stoi(optionValue); + if (len < MIN_PWD_ITERATIONS) + { + len = MIN_PWD_ITERATIONS; + std::cout << "Password iteration adjusted to minimum " << len << std::endl; + } + _adminConfig.pwdIterations = len; + } + else if (optionName == "pwd-hash-length") + { + unsigned len = std::stoi(optionValue); + if (len < MIN_PWD_HASH_LENGTH) + { + len = MIN_PWD_HASH_LENGTH; + std::cout << "Password hash length adjusted to minimum " << len << std::endl; + } + _adminConfig.pwdHashLength = len; + } +} + +int Config::main(const std::vector<std::string>& args) +{ + if (args.empty()) + { + std::cerr << "Nothing to do." << std::endl; + displayHelp(); + return Application::EXIT_NOINPUT; + } + + _loolConfig.load(ConfigFile); + + for (unsigned i = 0; i < args.size(); i++) { + if (args[i] == "set-admin-password") + { + unsigned char pwdhash[_adminConfig.pwdHashLength]; + unsigned char salt[_adminConfig.pwdSaltLength]; + RAND_bytes(salt, _adminConfig.pwdSaltLength); + std::stringstream stream; + + // Ask for user password + termios oldTermios; + tcgetattr(STDIN_FILENO, &oldTermios); + termios newTermios = oldTermios; + // Disable user input mirroring on console for password input + newTermios.c_lflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &newTermios); + std::string adminPwd; + std::cout << "Enter admin password: "; + std::cin >> adminPwd; + std::string reAdminPwd; + std::cout << std::endl << "Confirm admin password: "; + std::cin >> reAdminPwd; + std::cout << std::endl; + // Set the termios to old state + tcsetattr(STDIN_FILENO, TCSANOW, &oldTermios); + if (adminPwd != reAdminPwd) + { + std::cout << "Password mismatch." << std::endl; + return Application::EXIT_DATAERR; + } + + // Do the magic ! + PKCS5_PBKDF2_HMAC(adminPwd.c_str(), -1, + salt, _adminConfig.pwdSaltLength, + _adminConfig.pwdIterations, + EVP_sha512(), + _adminConfig.pwdHashLength, pwdhash); + + // Make salt randomness readable + for (unsigned j = 0; j < _adminConfig.pwdSaltLength; ++j) + stream << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(salt[j]); + const std::string saltHash = stream.str(); + + // Clear our used hex stream to make space for password hash + stream.str(""); + stream.clear(); + + // Make the hashed password readable + for (unsigned j = 0; j < _adminConfig.pwdHashLength; ++j) + stream << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(pwdhash[j]); + const std::string passwordHash = stream.str(); + + const std::string pwdConfigValue = "pbkdf2.sha512." + + std::to_string(_adminConfig.pwdIterations) + "." + + saltHash + "." + passwordHash; + _loolConfig.setString("admin_console.secure_password[@desc]", + "Salt and password hash combination generated using PBKDF2 with SHA512 digest."); + _loolConfig.setString("admin_console.secure_password", pwdConfigValue); + + std::cout << "Saving configuration to : " << ConfigFile << " ..." << std::endl; + _loolConfig.save(ConfigFile); + } + } + + // This tool only handles options, nothing to do here + return Application::EXIT_OK; +} + +POCO_APP_MAIN(Config); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits