forked from moonpower/azahar-UWP
310 lines
10 KiB
C++
310 lines
10 KiB
C++
//FILE MODIFIED BY AzaharPlus APRIL 2025
|
|
|
|
// Copyright 2020 Citra Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <sstream>
|
|
#include <boost/iostreams/device/file_descriptor.hpp>
|
|
#include <boost/iostreams/stream.hpp>
|
|
#include <cryptopp/hex.h>
|
|
#include <cryptopp/integer.h>
|
|
#include <cryptopp/nbtheory.h>
|
|
#include <cryptopp/sha.h>
|
|
#include <fmt/format.h>
|
|
#include "common/common_paths.h"
|
|
#include "common/file_util.h"
|
|
#include "common/logging/log.h"
|
|
#include "common/string_util.h"
|
|
#include "core/hw/aes/key.h"
|
|
#include "core/hw/rsa/rsa.h"
|
|
#include "cryptopp/osrng.h"
|
|
#include "cryptopp/rsa.h"
|
|
|
|
namespace HW::RSA {
|
|
|
|
namespace {
|
|
std::vector<u8> HexToBytes(const std::string& hex) {
|
|
std::vector<u8> bytes;
|
|
|
|
for (unsigned int i = 0; i < hex.length(); i += 2) {
|
|
std::string byteString = hex.substr(i, 2);
|
|
u8 byte = static_cast<u8>(std::strtol(byteString.c_str(), nullptr, 16));
|
|
bytes.push_back(byte);
|
|
}
|
|
return bytes;
|
|
};
|
|
} // namespace
|
|
|
|
constexpr std::size_t SlotSize = 4;
|
|
std::array<RsaSlot, SlotSize> rsa_slots;
|
|
|
|
RsaSlot ticket_wrap_slot;
|
|
RsaSlot secure_info_slot;
|
|
RsaSlot local_friend_code_seed_slot;
|
|
|
|
std::vector<u8> RsaSlot::ModularExponentiation(std::span<const u8> message,
|
|
int out_size_bytes) const {
|
|
CryptoPP::Integer sig =
|
|
CryptoPP::ModularExponentiation(CryptoPP::Integer(message.data(), message.size()),
|
|
CryptoPP::Integer(exponent.data(), exponent.size()),
|
|
CryptoPP::Integer(modulus.data(), modulus.size()));
|
|
|
|
std::vector<u8> result((out_size_bytes == -1) ? sig.MinEncodedSize() : out_size_bytes);
|
|
sig.Encode(result.data(), result.size());
|
|
return result;
|
|
}
|
|
|
|
std::vector<u8> RsaSlot::Sign(std::span<const u8> message) const {
|
|
if (private_d.empty()) {
|
|
LOG_ERROR(HW, "Cannot sign, RSA slot does not have a private key");
|
|
return {};
|
|
}
|
|
|
|
CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::PrivateKey private_key;
|
|
private_key.Initialize(CryptoPP::Integer(modulus.data(), modulus.size()),
|
|
CryptoPP::Integer(exponent.data(), exponent.size()),
|
|
CryptoPP::Integer(private_d.data(), private_d.size()));
|
|
|
|
CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::Signer signer(private_key);
|
|
CryptoPP::AutoSeededRandomPool prng;
|
|
std::vector<u8> ret(signer.SignatureLength());
|
|
|
|
signer.SignMessage(prng, message.data(), message.size(), ret.data());
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool RsaSlot::Verify(std::span<const u8> message, std::span<const u8> signature) const {
|
|
CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::PublicKey public_key;
|
|
public_key.Initialize(CryptoPP::Integer(modulus.data(), modulus.size()),
|
|
CryptoPP::Integer(exponent.data(), exponent.size()));
|
|
|
|
CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::Verifier verifier(public_key);
|
|
|
|
return verifier.VerifyMessage(message.data(), message.size(), signature.data(),
|
|
signature.size());
|
|
}
|
|
|
|
std::vector<u8> HexToVector(const std::string& hex) {
|
|
std::vector<u8> vector(hex.size() / 2);
|
|
for (std::size_t i = 0; i < vector.size(); ++i) {
|
|
vector[i] = static_cast<u8>(std::stoi(hex.substr(i * 2, 2), nullptr, 16));
|
|
}
|
|
|
|
return vector;
|
|
}
|
|
|
|
std::optional<std::pair<std::size_t, char>> ParseKeySlotName(const std::string& full_name) {
|
|
std::size_t slot;
|
|
char type;
|
|
int end;
|
|
if (std::sscanf(full_name.c_str(), "slot0x%zX%c%n", &slot, &type, &end) == 2 &&
|
|
end == static_cast<int>(full_name.size())) {
|
|
return std::make_pair(slot, type);
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
std::vector<u8> RsaSlot::GetSignature(std::span<const u8> message) const {
|
|
CryptoPP::Integer sig =
|
|
CryptoPP::ModularExponentiation(CryptoPP::Integer(message.data(), message.size()),
|
|
CryptoPP::Integer(exponent.data(), exponent.size()),
|
|
CryptoPP::Integer(modulus.data(), modulus.size()));
|
|
std::stringstream ss;
|
|
ss << std::hex << sig;
|
|
CryptoPP::HexDecoder decoder;
|
|
decoder.Put(reinterpret_cast<unsigned char*>(ss.str().data()), ss.str().size());
|
|
decoder.MessageEnd();
|
|
std::vector<u8> result(decoder.MaxRetrievable());
|
|
decoder.Get(result.data(), result.size());
|
|
return HexToBytes(ss.str());
|
|
}
|
|
|
|
// todotodo
|
|
#ifdef todotodo
|
|
void InitSlots() {
|
|
static bool initialized = false;
|
|
if (initialized)
|
|
return;
|
|
initialized = true;
|
|
|
|
auto s = HW::AES::GetKeysStream();
|
|
|
|
std::string mode = "";
|
|
|
|
while (!s.eof()) {
|
|
std::string line;
|
|
std::getline(s, line);
|
|
|
|
// Ignore empty or commented lines.
|
|
if (line.empty() || line.starts_with("#")) {
|
|
continue;
|
|
}
|
|
|
|
if (line.starts_with(":")) {
|
|
mode = line.substr(1);
|
|
continue;
|
|
}
|
|
|
|
if (mode != "RSA") {
|
|
continue;
|
|
}
|
|
|
|
const auto parts = Common::SplitString(line, '=');
|
|
if (parts.size() != 2) {
|
|
LOG_ERROR(HW_RSA, "Failed to parse {}", line);
|
|
continue;
|
|
}
|
|
|
|
const std::string& name = parts[0];
|
|
|
|
std::vector<u8> key;
|
|
try {
|
|
key = HexToVector(parts[1]);
|
|
} catch (const std::logic_error& e) {
|
|
LOG_ERROR(HW_RSA, "Invalid key {}: {}", parts[1], e.what());
|
|
continue;
|
|
}
|
|
|
|
if (name == "ticketWrapExp") {
|
|
ticket_wrap_slot.SetExponent(key);
|
|
continue;
|
|
}
|
|
|
|
if (name == "ticketWrapMod") {
|
|
ticket_wrap_slot.SetModulus(key);
|
|
continue;
|
|
}
|
|
|
|
if (name == "secureInfoExp") {
|
|
secure_info_slot.SetExponent(key);
|
|
continue;
|
|
}
|
|
|
|
if (name == "secureInfoMod") {
|
|
secure_info_slot.SetModulus(key);
|
|
continue;
|
|
}
|
|
|
|
if (name == "lfcsExp") {
|
|
local_friend_code_seed_slot.SetExponent(key);
|
|
continue;
|
|
}
|
|
|
|
if (name == "lfcsMod") {
|
|
local_friend_code_seed_slot.SetModulus(key);
|
|
continue;
|
|
}
|
|
|
|
const auto key_slot = ParseKeySlotName(name);
|
|
if (!key_slot) {
|
|
LOG_ERROR(HW_RSA, "Invalid key name '{}'", name);
|
|
continue;
|
|
}
|
|
|
|
if (key_slot->first >= SlotSize) {
|
|
LOG_ERROR(HW_RSA, "Out of range key slot ID {:#X}", key_slot->first);
|
|
continue;
|
|
}
|
|
|
|
switch (key_slot->second) {
|
|
case 'X':
|
|
rsa_slots.at(key_slot->first).SetExponent(key);
|
|
break;
|
|
case 'M':
|
|
rsa_slots.at(key_slot->first).SetModulus(key);
|
|
break;
|
|
case 'P':
|
|
rsa_slots.at(key_slot->first).SetPrivateD(key);
|
|
break;
|
|
default:
|
|
LOG_ERROR(HW_RSA, "Invalid key type '{}'", key_slot->second);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//--
|
|
void InitSlots() {
|
|
static bool initialized = false;
|
|
if (initialized)
|
|
return;
|
|
initialized = true;
|
|
|
|
const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + BOOTROM9;
|
|
FileUtil::IOFile file(filepath, "rb");
|
|
if (!file) {
|
|
return;
|
|
}
|
|
|
|
const std::size_t length = file.GetSize();
|
|
if (length != 65536) {
|
|
LOG_ERROR(HW_AES, "Bootrom9 size is wrong: {}", length);
|
|
return;
|
|
}
|
|
|
|
constexpr std::size_t RSA_MODULUS_POS = 0xB3E0;
|
|
file.Seek(RSA_MODULUS_POS, SEEK_SET);
|
|
std::vector<u8> modulus(256);
|
|
file.ReadArray(modulus.data(), modulus.size());
|
|
|
|
constexpr std::size_t RSA_EXPONENT_POS = 0xB4E0;
|
|
file.Seek(RSA_EXPONENT_POS, SEEK_SET);
|
|
std::vector<u8> exponent(256);
|
|
file.ReadArray(exponent.data(), exponent.size());
|
|
|
|
rsa_slots[0] = RsaSlot(std::move(exponent), std::move(modulus));
|
|
// TODO(B3N30): Initalize the other slots. But since they aren't used at all, we can skip them
|
|
// for now
|
|
}
|
|
//--
|
|
|
|
static RsaSlot empty_slot;
|
|
const RsaSlot& GetSlot(std::size_t slot_id) {
|
|
if (slot_id >= rsa_slots.size())
|
|
return empty_slot;
|
|
return rsa_slots[slot_id];
|
|
}
|
|
|
|
std::vector<u8> CreateASN1Message(std::span<const u8> data) {
|
|
static constexpr std::array<u8, 224> asn1_header = {
|
|
{0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0x31, 0x30, 0x0D, 0x06,
|
|
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}};
|
|
|
|
std::vector<u8> message(asn1_header.begin(), asn1_header.end());
|
|
CryptoPP::SHA256 sha;
|
|
message.resize(message.size() + CryptoPP::SHA256::DIGESTSIZE);
|
|
sha.CalculateDigest(message.data() + asn1_header.size(), data.data(), data.size());
|
|
return message;
|
|
}
|
|
|
|
const RsaSlot& GetTicketWrapSlot() {
|
|
return ticket_wrap_slot;
|
|
}
|
|
|
|
const RsaSlot& GetSecureInfoSlot() {
|
|
return secure_info_slot;
|
|
}
|
|
|
|
const RsaSlot& GetLocalFriendCodeSeedSlot() {
|
|
return local_friend_code_seed_slot;
|
|
}
|
|
|
|
} // namespace HW::RSA
|