Password file versioning, support for old versions
authorPiotr Bartosiewicz <p.bartosiewi@partner.samsung.com>
Wed, 11 Dec 2013 14:26:10 +0000 (15:26 +0100)
committerBartlomiej Grzelewski <b.grzelewski@samsung.com>
Thu, 6 Feb 2014 16:13:25 +0000 (17:13 +0100)
[Issue#]        SSDWSSP-711
[Bug/Feature]   Security server crashes after update to version with a
                new password file format.
[Cause]         There was no backward compatibility support.
[Solution]      Introduce a new password file format with versioning and
                algorithm identifier associated with every password
                entry. Additionally security server can read all old
                password file formats, but can only save in the newest.
                Also incorrect file mask was fixed.
[Verification]  Build, install, run tests. Check migration from old
                password file formats to the new one (folder:
                /opt/data/security-server, old file name: password.pwd,
                new file name: password).

Change-Id: If25952ddc467172fc8032fb98a31e9413762882e

src/server/dpl/core/include/dpl/serialization.h
src/server/main/server2-main.cpp
src/server/service/password-file.cpp
src/server/service/password-file.h

index 5c2a0ff..0c60c4e 100644 (file)
@@ -26,6 +26,7 @@
 #include <vector>
 #include <list>
 #include <map>
+#include <memory>
 
 namespace SecurityServer {
 // Abstract data stream buffer
@@ -203,6 +204,13 @@ struct Serialization {
     {
         Serialize(stream, *map);
     }
+
+    // std::unique_ptr
+    template <typename T>
+    static void Serialize(IStream& stream, const std::unique_ptr<T>& p)
+    {
+        Serialize(stream, *p);
+    }
 }; // struct Serialization
 
 struct Deserialization {
@@ -321,7 +329,7 @@ struct Deserialization {
         for (int i = 0; i < length; ++i) {
             T obj;
             Deserialize(stream, obj);
-            list.push_back(obj);
+            list.push_back(std::move(obj));
         }
     }
     template <typename T>
@@ -340,7 +348,7 @@ struct Deserialization {
         for (int i = 0; i < length; ++i) {
             T obj;
             Deserialize(stream, obj);
-            vec.push_back(obj);
+            vec.push_back(std::move(obj));
         }
     }
     template <typename T>
@@ -375,7 +383,7 @@ struct Deserialization {
             T obj;
             Deserialize(stream, key);
             Deserialize(stream, obj);
-            map[key] = obj;
+            map[key] = std::move(obj);
         }
     }
     template <typename K, typename T>
index 9d5453f..8400489 100644 (file)
 
 IMPLEMENT_SAFE_SINGLETON(SecurityServer::Log::LogSystem);
 
+#define REGISTER_SOCKET_SERVICE(manager, service) \
+    registerSocketService<service>(manager, #service)
+
+template<typename T>
+void registerSocketService(SecurityServer::SocketManager &manager, const std::string& serviceName)
+{
+    T *service = NULL;
+    try {
+        service = new T();
+        service->Create();
+        manager.RegisterSocketService(service);
+        service = NULL;
+    } catch (const SecurityServer::Exception &exception) {
+        LogError("Error in creating service " << serviceName <<
+                 ", details:\n" << exception.DumpToString());
+    } catch (const std::exception& e) {
+        LogError("Error in creating service " << serviceName <<
+                 ", details:\n" << e.what());
+    } catch (...) {
+        LogError("Error in creating service " << serviceName <<
+                 ", unknown exception occured");
+    }
+    if (service)
+        delete service;
+}
+
 int main(void) {
 
     UNHANDLED_EXCEPTION_HANDLER_BEGIN
@@ -68,33 +94,13 @@ int main(void) {
         LogInfo("Start!");
         SecurityServer::SocketManager manager;
 
-        SecurityServer::OpenForService *openForService = new SecurityServer::OpenForService;
-        openForService->Create();
-        manager.RegisterSocketService(openForService);
-
-        SecurityServer::CookieService *cookieService = new SecurityServer::CookieService;
-        cookieService->Create();
-        manager.RegisterSocketService(cookieService);
-
-        SecurityServer::SharedMemoryService *shmService = new SecurityServer::SharedMemoryService;
-        shmService->Create();
-        manager.RegisterSocketService(shmService);
-
-        SecurityServer::GetGidService *getGidService = new SecurityServer::GetGidService;
-        getGidService->Create();
-        manager.RegisterSocketService(getGidService);
-
-        SecurityServer::PrivilegeByPidService *privByPidService = new SecurityServer::PrivilegeByPidService;
-        privByPidService->Create();
-        manager.RegisterSocketService(privByPidService);
-
-        SecurityServer::AppPermissionsService *appEnablePermissionsService = new SecurityServer::AppPermissionsService;
-        appEnablePermissionsService->Create();
-        manager.RegisterSocketService(appEnablePermissionsService);
-
-        SecurityServer::PasswordService *pwdService = new SecurityServer::PasswordService;
-        pwdService->Create();
-        manager.RegisterSocketService(pwdService);
+        REGISTER_SOCKET_SERVICE(manager, SecurityServer::OpenForService);
+        REGISTER_SOCKET_SERVICE(manager, SecurityServer::CookieService);
+        REGISTER_SOCKET_SERVICE(manager, SecurityServer::SharedMemoryService);
+        REGISTER_SOCKET_SERVICE(manager, SecurityServer::GetGidService);
+        REGISTER_SOCKET_SERVICE(manager, SecurityServer::PrivilegeByPidService);
+        REGISTER_SOCKET_SERVICE(manager, SecurityServer::AppPermissionsService);
+        REGISTER_SOCKET_SERVICE(manager, SecurityServer::PasswordService);
 
         manager.MainLoop();
     }
index 87c1f32..e74c537 100644 (file)
@@ -19,6 +19,7 @@
  * @file        password-file.cpp
  * @author      Zbigniew Jasinski (z.jasinski@samsung.com)
  * @author      Lukasz Kostyra (l.kostyra@partner.samsung.com)
+ * @author      Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
  * @version     1.0
  * @brief       Implementation of PasswordFile, used to manage password files.
  */
 #include <fcntl.h>
 #include <string.h>
 
-const std::string DATA_DIR = "/opt/data/security-server";
-const std::string PASSWORD_FILE = "password.pwd";
-const std::string ATTEMPT_FILE = "attempt";
-const double RETRY_TIMEOUT = 0.5;
+namespace {
+    const std::string DATA_DIR = "/opt/data/security-server";
+    const std::string PASSWORD_FILE = DATA_DIR + "/password";
+    const std::string OLD_VERSION_PASSWORD_FILE = DATA_DIR + "/password.pwd";
+    const std::string ATTEMPT_FILE = DATA_DIR + "/attempt";
+    const double RETRY_TIMEOUT = 0.5;
+    const mode_t FILE_MODE = S_IRUSR | S_IWUSR;
+    const unsigned int CURRENT_FILE_VERSION = 3;
+}
 
 namespace SecurityServer
 {
-    PasswordFile::Password::Password()
+    class NoPassword: public IPassword
     {
-        m_password = PasswordFile::hashPassword("");
-    }
+        public:
+            NoPassword(IStream&) {}
+            NoPassword() {}
 
-    PasswordFile::Password::Password(const RawHash& password)
-    {
-        m_password = password;
-    }
+            void Serialize(IStream &stream) const
+            {
+                Serialization::Serialize(stream, static_cast<unsigned int>(PasswordType::NONE));
+            }
 
-    PasswordFile::Password::Password(IStream& stream)
-    {
-        Deserialization::Deserialize(stream, m_password);
-    }
+            bool match(const std::string &) const
+            {
+                return false;
+            }
+    };
 
-    void PasswordFile::Password::Serialize(IStream &stream) const
+    class SHA256Password: public IPassword
     {
-        Serialization::Serialize(stream, m_password);
+        public:
+            SHA256Password(IStream& stream)
+            {
+                Deserialization::Deserialize(stream, m_hash);
+            }
+
+            SHA256Password(const std::string &password)
+                : m_hash(hash(password)) {}
+
+            SHA256Password(const RawHash& hash)
+                : m_hash(hash) {}
+
+            void Serialize(IStream &stream) const
+            {
+                Serialization::Serialize(stream, static_cast<unsigned int>(PasswordType::SHA256));
+                Serialization::Serialize(stream, m_hash);
+            }
+
+            bool match(const std::string &password) const
+            {
+                return m_hash == hash(password);
+            }
+        private:
+            RawHash m_hash;
+
+            static RawHash hash(const std::string &password)
+            {
+                RawHash result(SHA256_DIGEST_LENGTH);
+
+                SHA256_CTX context;
+                SHA256_Init(&context);
+                SHA256_Update(&context, reinterpret_cast<const unsigned char*>(password.c_str()),
+                        password.size());
+                SHA256_Final(result.data(), &context);
+
+                return result;
+            }
+    };
+
+    // deserialization of new password format
+    template <>
+    void Deserialization::Deserialize(IStream& stream, IPasswordPtr& ptr)
+    {
+        unsigned int algorithm;
+        Deserialization::Deserialize(stream, algorithm);
+        switch (algorithm) {
+            case IPassword::PasswordType::NONE:
+                ptr.reset(new NoPassword());
+                break;
+            case IPassword::PasswordType::SHA256:
+                ptr.reset(new SHA256Password(stream));
+                break;
+            default:
+                Throw(PasswordException::FStreamReadError);
+        }
     }
 
-    PasswordFile::PasswordFile(): m_maxAttempt(PASSWORD_INFINITE_ATTEMPT_COUNT),
+    PasswordFile::PasswordFile(): m_passwordCurrent(new NoPassword()),
+                                  m_maxAttempt(PASSWORD_INFINITE_ATTEMPT_COUNT),
                                   m_maxHistorySize(0),
                                   m_expireTime(PASSWORD_INFINITE_EXPIRATION_TIME),
                                   m_passwordActive(false), m_attempt(0)
@@ -87,6 +150,15 @@ namespace SecurityServer
         resetTimer();
     }
 
+    void PasswordFile::resetState()
+    {
+        m_passwordCurrent.reset(new NoPassword());
+        m_maxAttempt = PASSWORD_INFINITE_ATTEMPT_COUNT;
+        m_maxHistorySize = 0;
+        m_expireTime = PASSWORD_INFINITE_EXPIRATION_TIME;
+        m_passwordActive = false;
+    }
+
     void PasswordFile::resetTimer()
     {
         m_retryTimerStart = std::chrono::monotonic_clock::now();
@@ -95,49 +167,55 @@ namespace SecurityServer
 
     void PasswordFile::preparePwdFile()
     {
-        std::string s_pwdfilePath = DATA_DIR + "/" + PASSWORD_FILE;
-
         // check if password file exists
-        // if not create it
-        if (!fileExists(s_pwdfilePath)) {
+        if (!fileExists(PASSWORD_FILE)) {
+            // if old format file exist - load it
+            if (tryLoadMemoryFromOldFormatFile()) {
+                // save in new format
+                writeMemoryToFile();
+                // and remove old file
+                remove(OLD_VERSION_PASSWORD_FILE.c_str());
+                return;
+            }
+
             LogSecureDebug("PWD_DBG not found password file. Creating.");
-            __mode_t oldMask = umask(S_IRUSR | S_IWUSR);
 
             //create file
             writeMemoryToFile();
-
-            umask(oldMask);
         } else {     //if file exists, load data
             LogSecureDebug("PWD_DBG found password file. Opening.");
-            loadMemoryFromFile();
+            try {
+                loadMemoryFromFile();
+            } catch (...) {
+                LogError("Invalid " << PASSWORD_FILE << " file format");
+                resetState();
+                writeMemoryToFile();
+            }
         }
     }
 
     void PasswordFile::prepareAttemptFile()
     {
-        std::string s_attemptfilePath = DATA_DIR + "/" + ATTEMPT_FILE;
-
         // check if attempt file exists
         // if not create it
-        if (!fileExists(s_attemptfilePath)) {
+        if (!fileExists(ATTEMPT_FILE)) {
             LogSecureDebug("PWD_DBG not found attempt file. Creating.");
-            __mode_t oldMask = umask(S_IRUSR | S_IWUSR);
 
             writeAttemptToFile();
-
-            umask(oldMask);
         } else {
             LogSecureDebug("PWD_DBG found attempt file. Opening.");
-            std::ifstream attemptFile(s_attemptfilePath);
-            if(!attemptFile.good()) {
+            std::ifstream attemptFile(ATTEMPT_FILE);
+            if(!attemptFile) {
                 LogError("Failed to open attempt file.");
-                Throw(PasswordException::FStreamOpenError);
+                // ignore error
+                return;
             }
 
             attemptFile.read(reinterpret_cast<char*>(&m_attempt), sizeof(unsigned int));
             if(!attemptFile) {
                 LogError("Failed to read attempt count.");
-                Throw(PasswordException::FStreamReadError);
+                // ignore error
+                resetAttempt();
             }
         }
     }
@@ -165,37 +243,108 @@ namespace SecurityServer
                        m_passwordActive);
 
         //serialize password attributes
+        Serialization::Serialize(pwdBuffer, CURRENT_FILE_VERSION);
         Serialization::Serialize(pwdBuffer, m_maxAttempt);
         Serialization::Serialize(pwdBuffer, m_maxHistorySize);
         Serialization::Serialize(pwdBuffer, m_expireTime);
         Serialization::Serialize(pwdBuffer, m_passwordActive);
-        Serialization::Serialize(pwdBuffer, m_passwords);
+        Serialization::Serialize(pwdBuffer, m_passwordCurrent);
+        Serialization::Serialize(pwdBuffer, m_passwordHistory);
 
-        pwdBuffer.Save(DATA_DIR + "/" + PASSWORD_FILE);
+        pwdBuffer.Save(PASSWORD_FILE);
+
+        chmod(PASSWORD_FILE.c_str(), FILE_MODE);
     }
 
     void PasswordFile::loadMemoryFromFile()
     {
         PasswordFileBuffer pwdFile;
 
-        pwdFile.Load(DATA_DIR + "/" + PASSWORD_FILE);
+        pwdFile.Load(PASSWORD_FILE);
+
+        unsigned int fileVersion = 0;
+        Deserialization::Deserialize(pwdFile, fileVersion);
+        if (fileVersion != CURRENT_FILE_VERSION)
+            Throw(PasswordException::FStreamReadError);
 
-        m_passwords.clear();
+        m_passwordHistory.clear();
 
         Deserialization::Deserialize(pwdFile, m_maxAttempt);
         Deserialization::Deserialize(pwdFile, m_maxHistorySize);
         Deserialization::Deserialize(pwdFile, m_expireTime);
         Deserialization::Deserialize(pwdFile, m_passwordActive);
-        Deserialization::Deserialize(pwdFile, m_passwords);
+        Deserialization::Deserialize(pwdFile, m_passwordCurrent);
+        Deserialization::Deserialize(pwdFile, m_passwordHistory);
 
         LogSecureDebug("Loaded max_att: " << m_maxAttempt << ", history_size: " <<
                        m_maxHistorySize << ", m_expireTime: " << m_expireTime << ", isActive: " <<
                        m_passwordActive);
     }
 
+    bool PasswordFile::tryLoadMemoryFromOldFormatFile()
+    {
+        struct stat oldFileStat;
+        if (stat(OLD_VERSION_PASSWORD_FILE.c_str(), &oldFileStat) != 0)
+            return false;
+
+        static const int ELEMENT_SIZE = sizeof(unsigned) + SHA256_DIGEST_LENGTH;
+        static const int VERSION_1_REMAINING = sizeof(unsigned) * 4;
+        static const int VERSION_2_REMAINING = VERSION_1_REMAINING + sizeof(bool);
+        int remaining = oldFileStat.st_size % ELEMENT_SIZE;
+
+        if (remaining != VERSION_1_REMAINING && remaining != VERSION_2_REMAINING)
+            return false;
+
+        try {
+            PasswordFileBuffer pwdFile;
+            pwdFile.Load(OLD_VERSION_PASSWORD_FILE);
+
+            Deserialization::Deserialize(pwdFile, m_maxAttempt);
+            Deserialization::Deserialize(pwdFile, m_maxHistorySize);
+            Deserialization::Deserialize(pwdFile, m_expireTime);
+            if (m_expireTime == 0)
+                m_expireTime = PASSWORD_INFINITE_EXPIRATION_TIME;
+            if (remaining == VERSION_2_REMAINING)
+                Deserialization::Deserialize(pwdFile, m_passwordActive);
+            else
+                m_passwordActive = true;
+
+            // deserialize passwords in old format
+            struct OldPassword {
+                OldPassword() {}
+                OldPassword(IStream &stream)
+                {
+                    Deserialization::Deserialize(stream, m_hash);
+                }
+                IPassword::RawHash m_hash;
+            };
+            std::list<OldPassword> oldFormatPasswords;
+            Deserialization::Deserialize(pwdFile, oldFormatPasswords);
+
+            // convert passwords to new format
+            m_passwordHistory.clear();
+            if (oldFormatPasswords.empty()) {
+                m_passwordCurrent.reset(new NoPassword());
+                m_passwordActive = false;
+            } else {
+                m_passwordCurrent.reset(new SHA256Password(oldFormatPasswords.front().m_hash));
+                std::for_each(++oldFormatPasswords.begin(), oldFormatPasswords.end(),
+                        [&m_passwordHistory] (const OldPassword& pwd)
+                        {m_passwordHistory.push_back(IPasswordPtr(new SHA256Password(pwd.m_hash)));}
+                        );
+            }
+        } catch (...) {
+            LogWarning("Invalid " << OLD_VERSION_PASSWORD_FILE << " file format");
+            resetState();
+            return false;
+        }
+
+        return true;
+    }
+
     void PasswordFile::writeAttemptToFile() const
     {
-        std::ofstream attemptFile(DATA_DIR + "/" + ATTEMPT_FILE, std::ofstream::trunc);
+        std::ofstream attemptFile(ATTEMPT_FILE, std::ofstream::trunc);
 
         if(!attemptFile.good()) {
             LogError("Failed to open attempt file.");
@@ -210,12 +359,13 @@ namespace SecurityServer
         attemptFile.close();
 
         int fd;
-        if (0 <= (fd = open((DATA_DIR + "/" + ATTEMPT_FILE).c_str(), O_WRONLY | O_APPEND))) {
+        if (0 <= (fd = open(ATTEMPT_FILE.c_str(), O_WRONLY | O_APPEND))) {
+            fchmod(fd, FILE_MODE);
             fsync(fd); // force synchronization system buffers with file
             close(fd);
         } else {
             int err = errno;
-            LogError("Failed to sync attempt file: " << DATA_DIR << "/" << ATTEMPT_FILE << "strerror: " << strerror(err));
+            LogError("Failed to sync attempt file: " << ATTEMPT_FILE << "strerror: " << strerror(err));
         }
     }
 
@@ -234,9 +384,8 @@ namespace SecurityServer
         //setting history should be independent from password being set
         m_maxHistorySize = history;
 
-        //we want to keep 1 current pwd, plus history amount of passwords.
-        if(m_passwords.size() > 1+history)
-            m_passwords.resize(1+history);
+        while(m_passwordHistory.size() > history)
+            m_passwordHistory.pop_back();
     }
 
     unsigned int PasswordFile::getMaxHistorySize() const
@@ -271,16 +420,12 @@ namespace SecurityServer
 
     bool PasswordFile::isPasswordReused(const std::string &password) const
     {
-        RawHash hashedPwd = hashPassword(password);
-
-        LogSecureDebug("Checking if pwd is reused. PwdCount: " << m_passwords.size() <<
-                       ", PwdMaxHistory: " << getMaxHistorySize());
-
-        auto history_beginning = (m_passwords.begin())++;
+        LogSecureDebug("Checking if pwd is reused. HistorySize: " << m_passwordHistory.size() <<
+                       ", MaxHistorySize: " << getMaxHistorySize());
 
-        if(std::find_if(history_beginning, m_passwords.end(),
-                        [&hashedPwd](const Password& pwd) { return (pwd.m_password == hashedPwd); })
-                != m_passwords.end()) {
+        //go through history and check if password existed earlier
+        if(std::any_of(m_passwordHistory.begin(), m_passwordHistory.end(),
+                       [&password](const IPasswordPtr& pwd) { return pwd->match(password); })) {
             LogSecureDebug("Passwords match!");
             return true;
         }
@@ -291,20 +436,20 @@ namespace SecurityServer
 
     void PasswordFile::setPassword(const std::string &password)
     {
-        RawHash hashedPwd = hashPassword(password);
+        //put current password to history
+        m_passwordHistory.push_front(std::move(m_passwordCurrent));
 
-        m_passwords.push_front(Password(hashedPwd));
+        //erase last password if we exceed max history size
+        if(m_passwordHistory.size() > getMaxHistorySize())
+            m_passwordHistory.pop_back();
 
-        //one current password, plus history amount of passwords
-        if(m_passwords.size() > 1+getMaxHistorySize())
-            m_passwords.pop_back();
+        //replace current password with new one
+        m_passwordCurrent.reset(new SHA256Password(password));
     }
 
     bool PasswordFile::checkPassword(const std::string &password) const
     {
-        RawHash hashedPwd = hashPassword(password);
-
-        return (hashedPwd == m_passwords.begin()->m_password);
+        return m_passwordCurrent->match(password);
     }
 
     void PasswordFile::setExpireTime(int expireTime)
@@ -351,20 +496,5 @@ namespace SecurityServer
     {
         return (m_maxHistorySize != 0);
     }
-
-    //hashPassword is also used in Password struct constructor, that's why it's static. Moreover
-    //it is assumed that incorrect input password was checked earlier.
-    PasswordFile::RawHash PasswordFile::hashPassword(const std::string &password)
-    {
-        RawHash result(SHA256_DIGEST_LENGTH);
-
-        SHA256_CTX context;
-        SHA256_Init(&context);
-        SHA256_Update(&context, reinterpret_cast<const unsigned char*>(password.c_str()),
-                      password.size());
-        SHA256_Final(result.data(), &context);
-
-        return result;
-    }
 } //namespace SecurityServer
 
index 0c385f1..e9f743b 100644 (file)
@@ -19,6 +19,7 @@
  * @file        password-file.h
  * @author      Zbigniew Jasinski (z.jasinski@samsung.com)
  * @author      Lukasz Kostyra (l.kostyra@partner.samsung.com)
+ * @author      Piotr Bartosiewicz (p.bartosiewi@partner.samsung.com)
  * @version     1.0
  * @brief       Implementation of PasswordFile, used to manage password files.
  */
@@ -29,6 +30,7 @@
 #include <vector>
 #include <list>
 #include <chrono>
+#include <memory>
 
 #include <time.h>
 
 
 namespace SecurityServer
 {
+    struct IPassword: public ISerializable
+    {
+        typedef std::vector<unsigned char> RawHash;
+
+        enum class PasswordType : unsigned int
+        {
+            NONE = 0,
+            SHA256 = 1,
+        };
+
+        virtual bool match(const std::string &password) const = 0;
+    };
+
+    typedef std::unique_ptr<IPassword> IPasswordPtr;
+    typedef std::list<IPasswordPtr> PasswordList;
+
     class PasswordFile
     {
     public:
@@ -72,40 +90,28 @@ namespace SecurityServer
         bool isHistoryActive() const;
 
     private:
-        typedef std::vector<unsigned char> RawHash;
         typedef std::chrono::duration<double> TimeDiff;
         typedef std::chrono::time_point<std::chrono::monotonic_clock, TimeDiff> TimePoint;
 
-        struct Password: public ISerializable
-        {
-            Password();
-            Password(const RawHash& password);
-            Password(IStream& stream);
-
-            virtual void Serialize(IStream &stream) const;
-
-            RawHash m_password;
-        };
-
-        typedef std::list<Password> PasswordList;
-
         void loadMemoryFromFile();
+        bool tryLoadMemoryFromOldFormatFile();
 
         void resetTimer();
         void preparePwdFile();
         void prepareAttemptFile();
+        void resetState();
         bool fileExists(const std::string &filename) const;
         bool dirExists(const std::string &dirpath) const;
-        static RawHash hashPassword(const std::string &password);
 
         mutable TimePoint m_retryTimerStart;
 
         //password file data
-        PasswordList m_passwords;
+        IPasswordPtr m_passwordCurrent;
+        PasswordList m_passwordHistory;
         unsigned int m_maxAttempt;
         unsigned int m_maxHistorySize;
-        time_t m_expireTime;
-        bool m_passwordActive;
+        time_t       m_expireTime;
+        bool         m_passwordActive;
 
         //attempt file data
         unsigned int m_attempt;