Encrypt downloaded apps with a console unique key

This commit is contained in:
PabloMK7
2025-03-01 13:12:52 +01:00
committed by GitHub
parent b8e138679c
commit 8ad53ca705
19 changed files with 427 additions and 142 deletions

View File

@@ -187,7 +187,7 @@ endif()
create_target_directory_groups(citra_common)
target_link_libraries(citra_common PUBLIC fmt library-headers microprofile Boost::boost Boost::serialization Boost::iostreams)
target_link_libraries(citra_common PRIVATE zstd)
target_link_libraries(citra_common PRIVATE cryptopp zstd)
if ("x86_64" IN_LIST ARCHITECTURE)
target_link_libraries(citra_common PRIVATE xbyak)

View File

@@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -13,7 +13,10 @@
#include <unordered_map>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include <fmt/format.h>
#include "common/archives.h"
#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/common_paths.h"
@@ -1136,7 +1139,7 @@ u64 IOFile::GetSize() const {
return 0;
}
bool IOFile::Seek(s64 off, int origin) {
bool IOFile::SeekImpl(s64 off, int origin) {
if (!IsOpen() || 0 != fseeko(m_file, off, origin))
m_good = false;
@@ -1240,6 +1243,107 @@ bool IOFile::Resize(u64 size) {
return m_good;
}
struct CryptoIOFileImpl {
std::vector<u8> key;
std::vector<u8> iv;
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d;
CryptoPP::CTR_Mode<CryptoPP::AES>::Encryption e;
std::vector<u8> write_buffer;
std::size_t ReadImpl(CryptoIOFile& f, void* data, std::size_t length, std::size_t data_size) {
std::size_t res = f.IOFile::ReadImpl(data, length, data_size);
if (res != std::numeric_limits<std::size_t>::max() && res != 0) {
d.ProcessData(reinterpret_cast<CryptoPP::byte*>(data),
reinterpret_cast<CryptoPP::byte*>(data), length * data_size);
e.Seek(f.IOFile::Tell());
}
return res;
}
std::size_t ReadAtImpl(CryptoIOFile& f, void* data, std::size_t length, std::size_t data_size,
std::size_t offset) {
std::size_t res = f.IOFile::ReadAtImpl(data, length, data_size, offset);
if (res != std::numeric_limits<std::size_t>::max() && res != 0) {
d.Seek(offset);
d.ProcessData(reinterpret_cast<CryptoPP::byte*>(data),
reinterpret_cast<CryptoPP::byte*>(data), length * data_size);
e.Seek(f.IOFile::Tell());
}
return res;
}
std::size_t WriteImpl(CryptoIOFile& f, const void* data, std::size_t length,
std::size_t data_size) {
if (write_buffer.size() < length * data_size) {
write_buffer.resize(length * data_size);
}
e.ProcessData(write_buffer.data(), reinterpret_cast<const CryptoPP::byte*>(data),
length * data_size);
std::size_t res = f.IOFile::WriteImpl(write_buffer.data(), length, data_size);
if (res != std::numeric_limits<std::size_t>::max() && res != 0) {
d.Seek(f.IOFile::Tell());
}
return res;
}
bool SeekImpl(CryptoIOFile& f, s64 off, int origin) {
bool res = f.IOFile::SeekImpl(off, origin);
if (res) {
u64 pos = f.IOFile::Tell();
d.Seek(pos);
e.Seek(pos);
}
return res;
}
};
CryptoIOFile::CryptoIOFile() : IOFile() {
impl = std::make_unique<CryptoIOFileImpl>();
}
CryptoIOFile::CryptoIOFile(const std::string& filename, const char openmode[],
const std::vector<u8>& aes_key, const std::vector<u8>& aes_iv, int flags)
: IOFile(filename, openmode, flags) {
impl = std::make_unique<CryptoIOFileImpl>();
impl->key = aes_key;
impl->iv = aes_iv;
impl->d.SetKeyWithIV(aes_key.data(), aes_key.size(), aes_iv.data());
impl->e.SetKeyWithIV(aes_key.data(), aes_key.size(), aes_iv.data());
}
CryptoIOFile::~CryptoIOFile() {}
std::size_t CryptoIOFile::ReadImpl(void* data, std::size_t length, std::size_t data_size) {
return impl->ReadImpl(*this, data, length, data_size);
}
std::size_t CryptoIOFile::ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
std::size_t offset) {
return impl->ReadAtImpl(*this, data, length, data_size, offset);
}
std::size_t CryptoIOFile::WriteImpl(const void* data, std::size_t length, std::size_t data_size) {
return impl->WriteImpl(*this, data, length, data_size);
}
bool CryptoIOFile::SeekImpl(s64 off, int origin) {
return impl->SeekImpl(*this, off, origin);
}
template <class Archive>
void CryptoIOFile::serialize(Archive& ar, const unsigned int) {
ar & impl->key;
ar & impl->iv;
if (Archive::is_loading::value) {
impl->e.SetKeyWithIV(impl->key.data(), impl->key.size(), impl->iv.data());
impl->d.SetKeyWithIV(impl->key.data(), impl->key.size(), impl->iv.data());
}
ar& boost::serialization::base_object<IOFile>(*this);
}
template <typename T>
using boost_iostreams = boost::iostreams::stream<T>;
@@ -1271,3 +1375,6 @@ void OpenFStream<std::ios_base::out>(
fstream.open(file_descriptor_sink);
}
} // namespace FileUtil
SERIALIZE_EXPORT_IMPL(FileUtil::IOFile)
SERIALIZE_EXPORT_IMPL(FileUtil::CryptoIOFile)

View File

@@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -12,14 +12,18 @@
#include <functional>
#include <ios>
#include <limits>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/split_member.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/wrapper.hpp>
#include "common/common_types.h"
#ifdef _MSC_VER
@@ -267,6 +271,8 @@ enum class DirectorySeparator {
std::string_view path,
DirectorySeparator directory_separator = DirectorySeparator::ForwardSlash);
struct CryptoIOFileImpl;
// simple wrapper for cstdlib file functions to
// hopefully will make error checking easier
// and make forgetting an fclose() harder
@@ -279,7 +285,7 @@ public:
// isn't considered "locked" while citra is open and people can open the log file and view it
IOFile(const std::string& filename, const char openmode[], int flags = 0);
~IOFile();
virtual ~IOFile();
IOFile(IOFile&& other) noexcept;
IOFile& operator=(IOFile&& other) noexcept;
@@ -369,14 +375,10 @@ public:
* @returns Count of T data successfully read.
*/
template <typename T>
[[nodiscard]] size_t ReadSpan(std::span<T> data) const {
[[nodiscard]] size_t ReadSpan(std::span<T> data) {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen()) {
return 0;
}
return std::fread(data.data(), sizeof(T), data.size(), m_file);
return ReadImpl(data.data(), data.size(), sizeof(T));
}
/**
@@ -395,14 +397,10 @@ public:
* @returns Count of T data successfully written.
*/
template <typename T>
[[nodiscard]] size_t WriteSpan(std::span<const T> data) const {
[[nodiscard]] size_t WriteSpan(std::span<const T> data) {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
if (!IsOpen()) {
return 0;
}
return std::fwrite(data.data(), sizeof(T), data.size(), m_file);
return WriteImpl(data.data(), data.size(), sizeof(T));
}
[[nodiscard]] bool IsOpen() const {
@@ -426,7 +424,9 @@ public:
return IsGood();
}
bool Seek(s64 off, int origin);
bool Seek(s64 off, int origin) {
return SeekImpl(off, origin);
}
[[nodiscard]] u64 Tell() const;
[[nodiscard]] u64 GetSize() const;
bool Resize(u64 size);
@@ -438,12 +438,24 @@ public:
std::clearerr(m_file);
}
private:
std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size);
std::size_t ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
std::size_t offset);
std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size);
virtual bool IsCrypto() {
return false;
}
const std::string& Filename() const {
return filename;
}
protected:
friend struct CryptoIOFileImpl;
virtual std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size);
virtual std::size_t ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
std::size_t offset);
virtual std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size);
virtual bool SeekImpl(s64 off, int origin);
private:
bool Open();
std::FILE* m_file = nullptr;
@@ -472,6 +484,37 @@ private:
friend class boost::serialization::access;
};
class CryptoIOFile : public IOFile {
public:
CryptoIOFile();
// flags is used for windows specific file open mode flags, which
// allows citra to open the logs in shared write mode, so that the file
// isn't considered "locked" while citra is open and people can open the log file and view it
CryptoIOFile(const std::string& filename, const char openmode[], const std::vector<u8>& aes_key,
const std::vector<u8>& aes_iv, int flags = 0);
bool IsCrypto() override {
return true;
}
~CryptoIOFile() override;
private:
std::unique_ptr<CryptoIOFileImpl> impl;
std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size) override;
std::size_t ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
std::size_t offset) override;
std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size) override;
bool SeekImpl(s64 off, int origin) override;
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
template <std::ios_base::openmode o, typename T>
void OpenFStream(T& fstream, const std::string& filename);
} // namespace FileUtil
@@ -485,3 +528,6 @@ void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmod
fstream.open(filename, openmode);
#endif
}
BOOST_CLASS_EXPORT_KEY(FileUtil::IOFile)
BOOST_CLASS_EXPORT_KEY(FileUtil::CryptoIOFile)