Implement SeedDB & Seed Crypto
This commit is contained in:
		@@ -69,6 +69,8 @@ add_library(core STATIC
 | 
			
		||||
    file_sys/romfs_reader.h
 | 
			
		||||
    file_sys/savedata_archive.cpp
 | 
			
		||||
    file_sys/savedata_archive.h
 | 
			
		||||
    file_sys/seed_db.cpp
 | 
			
		||||
    file_sys/seed_db.h
 | 
			
		||||
    file_sys/ticket.cpp
 | 
			
		||||
    file_sys/ticket.h
 | 
			
		||||
    file_sys/title_metadata.cpp
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,12 @@
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <cryptopp/aes.h>
 | 
			
		||||
#include <cryptopp/modes.h>
 | 
			
		||||
#include <cryptopp/sha.h>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/file_sys/ncch_container.h"
 | 
			
		||||
#include "core/file_sys/seed_db.h"
 | 
			
		||||
#include "core/hw/aes/key.h"
 | 
			
		||||
#include "core/loader/loader.h"
 | 
			
		||||
 | 
			
		||||
@@ -166,9 +168,19 @@ Loader::ResultStatus NCCHContainer::Load() {
 | 
			
		||||
                if (!ncch_header.seed_crypto) {
 | 
			
		||||
                    key_y_secondary = key_y_primary;
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Seed crypto is unimplemented.
 | 
			
		||||
                    LOG_ERROR(Service_FS, "Unsupported seed crypto");
 | 
			
		||||
                    failed_to_decrypt = true;
 | 
			
		||||
                    auto opt{FileSys::GetSeed(ncch_header.program_id)};
 | 
			
		||||
                    if (!opt.has_value()) {
 | 
			
		||||
                        LOG_ERROR(Service_FS, "Seed for program {:016X} not found",
 | 
			
		||||
                                  ncch_header.program_id);
 | 
			
		||||
                        failed_to_decrypt = true;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        auto seed{*opt};
 | 
			
		||||
                        std::array<u8, 32> input;
 | 
			
		||||
                        std::memcpy(input.data(), key_y_primary.data(), key_y_primary.size());
 | 
			
		||||
                        std::memcpy(input.data() + key_y_primary.size(), seed.data(), seed.size());
 | 
			
		||||
                        CryptoPP::SHA256 sha;
 | 
			
		||||
                        sha.CalculateDigest(key_y_secondary.data(), input.data(), input.size());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                SetKeyY(KeySlotID::NCCHSecure1, key_y_primary);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										149
									
								
								src/core/file_sys/seed_db.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								src/core/file_sys/seed_db.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,149 @@
 | 
			
		||||
// Copyright 2018 Citra Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <fmt/format.h>
 | 
			
		||||
#include "common/file_util.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "core/file_sys/seed_db.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
bool SeedDB::Load() {
 | 
			
		||||
    seeds.clear();
 | 
			
		||||
    const std::string path{
 | 
			
		||||
        fmt::format("{}/seeddb.bin", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir))};
 | 
			
		||||
    if (!FileUtil::Exists(path)) {
 | 
			
		||||
        if (!FileUtil::CreateFullPath(path)) {
 | 
			
		||||
            LOG_ERROR(Service_FS, "Failed to create seed database");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (!Save()) {
 | 
			
		||||
            LOG_ERROR(Service_FS, "Failed to save seed database");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    FileUtil::IOFile file{path, "rb"};
 | 
			
		||||
    if (!file.IsOpen()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Failed to open seed database");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    u32 count;
 | 
			
		||||
    if (!file.ReadBytes(&count, sizeof(count))) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Failed to read seed database count fully");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    if (!file.Seek(file.Tell() + SEEDDB_PADDING_BYTES, SEEK_SET)) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Failed to skip seed database padding");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    for (u32 i = 0; i < count; ++i) {
 | 
			
		||||
        Seed seed;
 | 
			
		||||
        if (!file.ReadBytes(&seed.title_id, sizeof(seed.title_id))) {
 | 
			
		||||
            LOG_ERROR(Service_FS, "Failed to read seed {} title ID", i);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (!file.ReadBytes(seed.data.data(), seed.data.size())) {
 | 
			
		||||
            LOG_ERROR(Service_FS, "Failed to read seed {} data", i);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (!file.ReadBytes(seed.reserved.data(), seed.reserved.size())) {
 | 
			
		||||
            LOG_ERROR(Service_FS, "Failed to read seed {} reserved data", i);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        seeds.push_back(seed);
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SeedDB::Save() {
 | 
			
		||||
    const std::string path{
 | 
			
		||||
        fmt::format("{}/seeddb.bin", FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir))};
 | 
			
		||||
    if (!FileUtil::CreateFullPath(path)) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Failed to create seed database");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    FileUtil::IOFile file{path, "wb"};
 | 
			
		||||
    if (!file.IsOpen()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Failed to open seed database");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    u32 count{static_cast<u32>(seeds.size())};
 | 
			
		||||
    if (file.WriteBytes(&count, sizeof(count)) != sizeof(count)) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Failed to write seed database count fully");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    std::array<u8, SEEDDB_PADDING_BYTES> padding{};
 | 
			
		||||
    if (file.WriteBytes(padding.data(), padding.size()) != padding.size()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Failed to write seed database padding fully");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    for (std::size_t i = 0; i < count; ++i) {
 | 
			
		||||
        if (file.WriteBytes(&seeds[i].title_id, sizeof(seeds[i].title_id)) !=
 | 
			
		||||
            sizeof(seeds[i].title_id)) {
 | 
			
		||||
            LOG_ERROR(Service_FS, "Failed to write seed {} title ID fully", i);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (file.WriteBytes(seeds[i].data.data(), seeds[i].data.size()) != seeds[i].data.size()) {
 | 
			
		||||
            LOG_ERROR(Service_FS, "Failed to write seed {} data fully", i);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (file.WriteBytes(seeds[i].reserved.data(), seeds[i].reserved.size()) !=
 | 
			
		||||
            seeds[i].reserved.size()) {
 | 
			
		||||
            LOG_ERROR(Service_FS, "Failed to write seed {} reserved data fully", i);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SeedDB::Add(const Seed& seed) {
 | 
			
		||||
    seeds.push_back(seed);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t SeedDB::GetCount() const {
 | 
			
		||||
    return seeds.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
auto SeedDB::FindSeedByTitleID(u64 title_id) const {
 | 
			
		||||
    return std::find_if(seeds.begin(), seeds.end(),
 | 
			
		||||
                        [title_id](const auto& seed) { return seed.title_id == title_id; });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool AddSeed(const Seed& seed) {
 | 
			
		||||
    // TODO: does this skip/replace if the SeedDB contains a seed for seed.title_id?
 | 
			
		||||
    SeedDB db;
 | 
			
		||||
    if (!db.Load()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Failed to load seed database");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    db.Add(seed);
 | 
			
		||||
    if (!db.Save()) {
 | 
			
		||||
        LOG_ERROR(Service_FS, "Failed to save seed database");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<Seed::Data> GetSeed(u64 title_id) {
 | 
			
		||||
    SeedDB db;
 | 
			
		||||
    if (!db.Load()) {
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
    const auto found_seed_iter{db.FindSeedByTitleID(title_id)};
 | 
			
		||||
    if (found_seed_iter != db.seeds.end()) {
 | 
			
		||||
        return found_seed_iter->data;
 | 
			
		||||
    }
 | 
			
		||||
    return std::nullopt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32 GetSeedCount() {
 | 
			
		||||
    SeedDB db;
 | 
			
		||||
    if (!db.Load()) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
    return db.GetCount();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
							
								
								
									
										38
									
								
								src/core/file_sys/seed_db.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/core/file_sys/seed_db.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
// Copyright 2018 Citra Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
constexpr std::size_t SEEDDB_PADDING_BYTES{12};
 | 
			
		||||
 | 
			
		||||
struct Seed {
 | 
			
		||||
    using Data = std::array<u8, 16>;
 | 
			
		||||
 | 
			
		||||
    u64_le title_id;
 | 
			
		||||
    Data data;
 | 
			
		||||
    std::array<u8, 8> reserved;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct SeedDB {
 | 
			
		||||
    std::vector<Seed> seeds;
 | 
			
		||||
 | 
			
		||||
    bool Load();
 | 
			
		||||
    bool Save();
 | 
			
		||||
    void Add(const Seed& seed);
 | 
			
		||||
 | 
			
		||||
    std::size_t GetCount() const;
 | 
			
		||||
    auto FindSeedByTitleID(u64 title_id) const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
bool AddSeed(const Seed& seed);
 | 
			
		||||
std::optional<Seed::Data> GetSeed(u64 title_id);
 | 
			
		||||
u32 GetSeedCount();
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
@@ -11,6 +11,7 @@
 | 
			
		||||
#include "common/string_util.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/file_sys/errors.h"
 | 
			
		||||
#include "core/file_sys/seed_db.h"
 | 
			
		||||
#include "core/hle/ipc.h"
 | 
			
		||||
#include "core/hle/ipc_helpers.h"
 | 
			
		||||
#include "core/hle/kernel/client_port.h"
 | 
			
		||||
@@ -685,14 +686,18 @@ void FS_USER::ObsoletedDeleteExtSaveData(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FS_USER::GetNumSeeds(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestParser rp(ctx, 0x87D, 0, 0);
 | 
			
		||||
 | 
			
		||||
    LOG_WARNING(Service_FS, "(STUBBED) called");
 | 
			
		||||
 | 
			
		||||
    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
 | 
			
		||||
 | 
			
		||||
    IPC::RequestBuilder rb{ctx, 0x87D, 2, 0};
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    rb.Push<u32>(FileSys::GetSeedCount());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FS_USER::AddSeed(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestParser rp{ctx, 0x87A, 6, 0};
 | 
			
		||||
    u64 title_id{rp.Pop<u64>()};
 | 
			
		||||
    FileSys::Seed::Data seed{rp.PopRaw<FileSys::Seed::Data>()};
 | 
			
		||||
    FileSys::AddSeed({title_id, seed, {}});
 | 
			
		||||
    IPC::RequestBuilder rb{rp.MakeBuilder(1, 0)};
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
    rb.Push<u32>(0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FS_USER::SetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
@@ -846,7 +851,7 @@ FS_USER::FS_USER() : ServiceFramework("fs:USER", 30) {
 | 
			
		||||
        {0x08680000, nullptr, "GetMediaType"},
 | 
			
		||||
        {0x08690000, nullptr, "GetNandEraseCount"},
 | 
			
		||||
        {0x086A0082, nullptr, "ReadNandReport"},
 | 
			
		||||
        {0x087A0180, nullptr, "AddSeed"},
 | 
			
		||||
        {0x087A0180, &FS_USER::AddSeed, "AddSeed"},
 | 
			
		||||
        {0x087D0000, &FS_USER::GetNumSeeds, "GetNumSeeds"},
 | 
			
		||||
        {0x088600C0, nullptr, "CheckUpdatedDat"},
 | 
			
		||||
    };
 | 
			
		||||
 
 | 
			
		||||
@@ -485,6 +485,18 @@ private:
 | 
			
		||||
     */
 | 
			
		||||
    void GetNumSeeds(Kernel::HLERequestContext& ctx);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * FS_User::AddSeed service function.
 | 
			
		||||
     *  Inputs:
 | 
			
		||||
     *      0 : 0x087A0180
 | 
			
		||||
     *    1-2 : u64, Title ID
 | 
			
		||||
     *    3-6 : Seed
 | 
			
		||||
     *  Outputs:
 | 
			
		||||
     *      0 : 0x087A0180
 | 
			
		||||
     *      1 : Result of function, 0 on success, otherwise error code
 | 
			
		||||
     */
 | 
			
		||||
    void AddSeed(Kernel::HLERequestContext& ctx);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * FS_User::SetSaveDataSecureValue service function.
 | 
			
		||||
     *  Inputs:
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user