key_manager: Make ticket reading more robust.
This commit is contained in:
parent
2f569f6384
commit
64f3ad2654
4 changed files with 140 additions and 96 deletions
|
@ -35,7 +35,6 @@ namespace Core::Crypto {
|
|||
namespace {
|
||||
|
||||
constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
|
||||
constexpr u64 FULL_TICKET_SIZE = 0x400;
|
||||
|
||||
using Common::AsArray;
|
||||
|
||||
|
@ -214,34 +213,51 @@ Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& righ
|
|||
return Ticket{out};
|
||||
}
|
||||
|
||||
bool Ticket::Read(Ticket& ticket_out, const FileSys::VirtualFile& file) {
|
||||
SignatureType sig_type;
|
||||
if (file->Read(reinterpret_cast<u8*>(&sig_type), sizeof(sig_type), 0) < sizeof(sig_type)) {
|
||||
return false;
|
||||
Ticket Ticket::Read(const FileSys::VirtualFile& file) {
|
||||
// Attempt to read up to the largest ticket size, and make sure we read at least a signature
|
||||
// type.
|
||||
std::array<u8, sizeof(RSA4096Ticket)> raw_data{};
|
||||
auto read_size = file->Read(raw_data.data(), raw_data.size(), 0);
|
||||
if (read_size < sizeof(SignatureType)) {
|
||||
LOG_WARNING(Crypto, "Attempted to read ticket file with invalid size {}.", read_size);
|
||||
return Ticket{std::monostate()};
|
||||
}
|
||||
return Read(std::span{raw_data});
|
||||
}
|
||||
|
||||
Ticket Ticket::Read(std::span<const u8> raw_data) {
|
||||
// Some tools read only 0x180 bytes of ticket data instead of 0x2C0, so
|
||||
// just make sure we have at least the bare minimum of data to work with.
|
||||
SignatureType sig_type;
|
||||
if (raw_data.size() < sizeof(SignatureType)) {
|
||||
LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid size {}.",
|
||||
raw_data.size());
|
||||
return Ticket{std::monostate()};
|
||||
}
|
||||
std::memcpy(&sig_type, raw_data.data(), sizeof(sig_type));
|
||||
|
||||
switch (sig_type) {
|
||||
case SignatureType::RSA_4096_SHA1:
|
||||
case SignatureType::RSA_4096_SHA256: {
|
||||
ticket_out.data.emplace<RSA4096Ticket>();
|
||||
file->Read(reinterpret_cast<u8*>(&ticket_out.data), sizeof(RSA4096Ticket), 0);
|
||||
return true;
|
||||
RSA4096Ticket ticket{};
|
||||
std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
|
||||
return Ticket{ticket};
|
||||
}
|
||||
case SignatureType::RSA_2048_SHA1:
|
||||
case SignatureType::RSA_2048_SHA256: {
|
||||
ticket_out.data.emplace<RSA2048Ticket>();
|
||||
file->Read(reinterpret_cast<u8*>(&ticket_out.data), sizeof(RSA2048Ticket), 0);
|
||||
return true;
|
||||
RSA2048Ticket ticket{};
|
||||
std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
|
||||
return Ticket{ticket};
|
||||
}
|
||||
case SignatureType::ECDSA_SHA1:
|
||||
case SignatureType::ECDSA_SHA256: {
|
||||
ticket_out.data.emplace<ECDSATicket>();
|
||||
file->Read(reinterpret_cast<u8*>(&ticket_out.data), sizeof(ECDSATicket), 0);
|
||||
return true;
|
||||
ECDSATicket ticket{};
|
||||
std::memcpy(&ticket, raw_data.data(), sizeof(ticket));
|
||||
return Ticket{ticket};
|
||||
}
|
||||
default:
|
||||
ticket_out.data.emplace<std::monostate>();
|
||||
return false;
|
||||
LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid type {}.", sig_type);
|
||||
return Ticket{std::monostate()};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -482,10 +498,12 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) {
|
|||
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
|
||||
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
|
||||
buffer[offset + 3] == 0x0) {
|
||||
out.emplace_back();
|
||||
auto& next = out.back();
|
||||
std::memcpy(&next, buffer.data() + offset, sizeof(Ticket));
|
||||
offset += FULL_TICKET_SIZE;
|
||||
// NOTE: Assumes ticket blob will only contain RSA-2048 tickets.
|
||||
auto ticket = Ticket::Read(std::span{buffer.data() + offset, sizeof(RSA2048Ticket)});
|
||||
offset += sizeof(RSA2048Ticket);
|
||||
if (ticket.IsValid()) {
|
||||
out.push_back(ticket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -544,8 +562,6 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Dirty hack, figure out why ticket.data variant is invalid
|
||||
try {
|
||||
const auto issuer = ticket.GetData().issuer;
|
||||
if (IsAllZeroArray(issuer)) {
|
||||
return std::nullopt;
|
||||
|
@ -606,9 +622,6 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
|
|||
std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
|
||||
|
||||
return std::make_pair(rights_id, key_temp);
|
||||
} catch (const std::bad_variant_access&) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
KeyManager::KeyManager() {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include <variant>
|
||||
|
@ -80,6 +81,7 @@ struct RSA4096Ticket {
|
|||
INSERT_PADDING_BYTES(0x3C);
|
||||
TicketData data;
|
||||
};
|
||||
static_assert(sizeof(RSA4096Ticket) == 0x500, "RSA4096Ticket has incorrect size.");
|
||||
|
||||
struct RSA2048Ticket {
|
||||
SignatureType sig_type;
|
||||
|
@ -87,6 +89,7 @@ struct RSA2048Ticket {
|
|||
INSERT_PADDING_BYTES(0x3C);
|
||||
TicketData data;
|
||||
};
|
||||
static_assert(sizeof(RSA2048Ticket) == 0x400, "RSA2048Ticket has incorrect size.");
|
||||
|
||||
struct ECDSATicket {
|
||||
SignatureType sig_type;
|
||||
|
@ -94,18 +97,41 @@ struct ECDSATicket {
|
|||
INSERT_PADDING_BYTES(0x40);
|
||||
TicketData data;
|
||||
};
|
||||
static_assert(sizeof(ECDSATicket) == 0x340, "ECDSATicket has incorrect size.");
|
||||
|
||||
struct Ticket {
|
||||
std::variant<std::monostate, RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
|
||||
|
||||
bool IsValid() const;
|
||||
SignatureType GetSignatureType() const;
|
||||
TicketData& GetData();
|
||||
const TicketData& GetData() const;
|
||||
u64 GetSize() const;
|
||||
[[nodiscard]] bool IsValid() const;
|
||||
[[nodiscard]] SignatureType GetSignatureType() const;
|
||||
[[nodiscard]] TicketData& GetData();
|
||||
[[nodiscard]] const TicketData& GetData() const;
|
||||
[[nodiscard]] u64 GetSize() const;
|
||||
|
||||
/**
|
||||
* Synthesizes a common ticket given a title key and rights ID.
|
||||
*
|
||||
* @param title_key Title key to store in the ticket.
|
||||
* @param rights_id Rights ID the ticket is for.
|
||||
* @return The synthesized common ticket.
|
||||
*/
|
||||
static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id);
|
||||
static bool Read(Ticket& ticket_out, const FileSys::VirtualFile& file);
|
||||
|
||||
/**
|
||||
* Reads a ticket from a file.
|
||||
*
|
||||
* @param file File to read the ticket from.
|
||||
* @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false.
|
||||
*/
|
||||
static Ticket Read(const FileSys::VirtualFile& file);
|
||||
|
||||
/**
|
||||
* Reads a ticket from a memory buffer.
|
||||
*
|
||||
* @param raw_data Buffer to read the ticket from.
|
||||
* @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false.
|
||||
*/
|
||||
static Ticket Read(std::span<const u8> raw_data);
|
||||
};
|
||||
|
||||
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
|
||||
|
|
|
@ -190,8 +190,8 @@ void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) {
|
|||
continue;
|
||||
}
|
||||
|
||||
Core::Crypto::Ticket ticket{};
|
||||
if (!Core::Crypto::Ticket::Read(ticket, ticket_file)) {
|
||||
auto ticket = Core::Crypto::Ticket::Read(ticket_file);
|
||||
if (!ticket.IsValid()) {
|
||||
LOG_WARNING(Common_Filesystem, "Could not read NSP ticket {}", ticket_file->GetName());
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -122,20 +122,25 @@ private:
|
|||
}
|
||||
|
||||
void ImportTicket(HLERequestContext& ctx) {
|
||||
const auto ticket = ctx.ReadBuffer();
|
||||
const auto raw_ticket = ctx.ReadBuffer();
|
||||
[[maybe_unused]] const auto cert = ctx.ReadBuffer(1);
|
||||
|
||||
if (ticket.size() < sizeof(Core::Crypto::Ticket)) {
|
||||
if (raw_ticket.size() < sizeof(Core::Crypto::Ticket)) {
|
||||
LOG_ERROR(Service_ETicket, "The input buffer is not large enough!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
Core::Crypto::Ticket raw{};
|
||||
std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket));
|
||||
Core::Crypto::Ticket ticket = Core::Crypto::Ticket::Read(raw_ticket);
|
||||
if (!ticket.IsValid()) {
|
||||
LOG_ERROR(Service_ETicket, "The ticket is invalid!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!keys.AddTicketPersonalized(raw)) {
|
||||
if (!keys.AddTicketPersonalized(ticket)) {
|
||||
LOG_ERROR(Service_ETicket, "The ticket could not be imported!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue