[Libreoffice-commits] online.git: 2 commits - common/Util.cpp common/Util.hpp .gitignore Makefile.am tools/Config.cpp wsd/FileServer.cpp

Pranav Kant pranavk at collabora.co.uk
Wed May 24 19:49:01 UTC 2017


 .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 <pranavk at 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 <pranavk at 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: */


More information about the Libreoffice-commits mailing list