// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C)
// 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025
// Eduardo Aguiar
//
// 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 2, 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 <http://www.gnu.org/licenses/>.
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include <mobius/core/charset.hpp>
#include <mobius/core/crypt/hash.hpp>
#include <mobius/core/exception.inc>
#include <mobius/core/os/win/dpapi/credhist_file.hpp>
#include <set>
#include <stdexcept>

namespace mobius::core::os::win::dpapi
{
namespace
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// @brief Try to decrypt all CREDHIST entries in sequence
// @param entries Reference to entries
// @return true if any entry has been decrypted, false if none
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static bool
decrypt_sequence (std::vector<credhist_entry> &entries)
{
    bool rc = false;

    // get hashes from decrypted entries
    std::set<mobius::core::bytearray> hashes;

    for (auto entry : entries)
    {
        if (entry.is_decrypted ())
            hashes.insert (entry.get_hash_sha1 ());
    }

    // test those hashes on non-decrypted entries
    while (!hashes.empty ())
    {
        auto iter_first = hashes.begin ();
        auto h = *iter_first;
        hashes.erase (iter_first);

        for (auto entry : entries)
        {
            if (!entry.is_decrypted () && entry.decrypt_with_password_hash (h))
            {
                hashes.insert (entry.get_hash_sha1 ());
                rc = true;
            }
        }
    }

    return rc;
}

} // namespace

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// @brief Constructor
// @param reader Reader object
//
// 1. CREDHIST files contain a single linked list of entries, starting from the
//    end of the file, up to the beginning of it.
// 2. Each entry has an entry footer (24 bytes) and optionally an entry data
//    block.
// 3. entry.size determines how many bytes to jump from current entry end
//    position to the next entry end position.
// 4. if entry.size > 0 then entry has data
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
credhist_file::credhist_file (mobius::core::io::reader reader)
{
    // Read first entry (control block only)
    constexpr int FOOTER_SIZE = 24;

    if (reader.get_size () < FOOTER_SIZE)
        throw std::runtime_error (
            MOBIUS_EXCEPTION_MSG ("Not enough bytes to read"));

    reader.seek (-FOOTER_SIZE, mobius::core::io::reader::whence_type::end);
    auto pos = reader.tell ();
    auto entry = credhist_entry (reader, FOOTER_SIZE);
    auto link_size = entry.get_next_link_size ();

    // Iterate through entries
    while (link_size > 0)
    {
        pos = pos - link_size;
        reader.seek (pos);
        entry = credhist_entry (reader, link_size);
        link_size = entry.get_next_link_size ();
        entries_.push_back (entry);
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// @brief Decrypt CREDHIST entries
// @param key Decryption key
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool
credhist_file::decrypt_with_key (const mobius::core::bytearray &key)
{
    bool rc = false;

    if (entries_.size ())
    {
        rc = entries_[0].decrypt_with_key (key);
        rc |= decrypt_sequence (entries_);
    }

    return rc;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// @brief Decrypt CREDHIST entries with password hash
// @param h Password hash
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool
credhist_file::decrypt_with_password_hash (const mobius::core::bytearray &h)
{
    bool rc = false;

    for (auto entry : entries_)
    {
        if (entry.decrypt_with_password_hash (h))
            rc = true;
    }

    rc |= decrypt_sequence (entries_);

    return rc;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// @brief Decrypt CREDHIST entries with password
// @param password Password
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
bool
credhist_file::decrypt_with_password (const std::string &password)
{
    mobius::core::crypt::hash h ("sha1");
    h.update (mobius::core::conv_charset (password, "UTF-8", "UTF-16LE"));

    return decrypt_with_password_hash (h.get_digest ());
}

} // namespace mobius::core::os::win::dpapi
