diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index ad28bae9b0..5132d344c9 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -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& righ return Ticket{out}; } -bool Ticket::Read(Ticket& ticket_out, const FileSys::VirtualFile& file) { - SignatureType sig_type; - if (file->Read(reinterpret_cast(&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 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 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(); - file->Read(reinterpret_cast(&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(); - file->Read(reinterpret_cast(&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(); - file->Read(reinterpret_cast(&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(); - 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 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,71 +562,66 @@ std::optional> 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; - } - if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { - LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); - } - - Key128 rights_id = ticket.GetData().rights_id; - - if (rights_id == Key128{}) { - return std::nullopt; - } - - if (ticket.GetData().type == TitleKeyType::Common) { - return std::make_pair(rights_id, ticket.GetData().title_key_common); - } - - mbedtls_mpi D; // RSA Private Exponent - mbedtls_mpi N; // RSA Modulus - mbedtls_mpi S; // Input - mbedtls_mpi M; // Output - - mbedtls_mpi_init(&D); - mbedtls_mpi_init(&N); - mbedtls_mpi_init(&S); - mbedtls_mpi_init(&M); - - mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); - mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); - mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100); - - mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); - - std::array rsa_step; - mbedtls_mpi_write_binary(&M, rsa_step.data(), rsa_step.size()); - - u8 m_0 = rsa_step[0]; - std::array m_1; - std::memcpy(m_1.data(), rsa_step.data() + 0x01, m_1.size()); - std::array m_2; - std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size()); - - if (m_0 != 0) { - return std::nullopt; - } - - m_1 = m_1 ^ MGF1<0x20>(m_2); - m_2 = m_2 ^ MGF1<0xDF>(m_1); - - const auto offset = FindTicketOffset(m_2); - if (!offset) { - return std::nullopt; - } - ASSERT(*offset > 0); - - Key128 key_temp{}; - 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&) { + const auto issuer = ticket.GetData().issuer; + if (IsAllZeroArray(issuer)) { return std::nullopt; } + if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { + LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); + } + + Key128 rights_id = ticket.GetData().rights_id; + + if (rights_id == Key128{}) { + return std::nullopt; + } + + if (ticket.GetData().type == TitleKeyType::Common) { + return std::make_pair(rights_id, ticket.GetData().title_key_common); + } + + mbedtls_mpi D; // RSA Private Exponent + mbedtls_mpi N; // RSA Modulus + mbedtls_mpi S; // Input + mbedtls_mpi M; // Output + + mbedtls_mpi_init(&D); + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&S); + mbedtls_mpi_init(&M); + + mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); + mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); + mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100); + + mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); + + std::array rsa_step; + mbedtls_mpi_write_binary(&M, rsa_step.data(), rsa_step.size()); + + u8 m_0 = rsa_step[0]; + std::array m_1; + std::memcpy(m_1.data(), rsa_step.data() + 0x01, m_1.size()); + std::array m_2; + std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size()); + + if (m_0 != 0) { + return std::nullopt; + } + + m_1 = m_1 ^ MGF1<0x20>(m_2); + m_2 = m_2 ^ MGF1<0xDF>(m_1); + + const auto offset = FindTicketOffset(m_2); + if (!offset) { + return std::nullopt; + } + ASSERT(*offset > 0); + + Key128 key_temp{}; + std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); + + return std::make_pair(rights_id, key_temp); } KeyManager::KeyManager() { diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 98afb5733e..52edc8b05a 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -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 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& 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 raw_data); }; static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index 4014bda0e6..b0f55df708 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -190,8 +190,8 @@ void NSP::SetTicketKeys(const std::vector& 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; } diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 446f46b3cc..896f870b5e 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -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);