2016-12-16 03:34:49 +01:00
|
|
|
#include "ticket.h"
|
2017-02-26 14:04:03 +01:00
|
|
|
#include "unittype.h"
|
2018-08-13 00:08:56 +02:00
|
|
|
#include "cert.h"
|
2017-02-06 03:16:35 +01:00
|
|
|
#include "sha.h"
|
2018-01-10 02:08:29 +01:00
|
|
|
#include "rsa.h"
|
2016-12-16 03:34:49 +01:00
|
|
|
#include "ff.h"
|
|
|
|
|
|
|
|
u32 ValidateTicket(Ticket* ticket) {
|
2020-07-23 13:46:42 -03:00
|
|
|
static const u8 magic[] = { TICKET_SIG_TYPE };
|
2016-12-16 03:34:49 +01:00
|
|
|
if ((memcmp(ticket->sig_type, magic, sizeof(magic)) != 0) ||
|
|
|
|
((strncmp((char*) ticket->issuer, TICKET_ISSUER, 0x40) != 0) &&
|
2017-06-09 00:21:31 +02:00
|
|
|
(strncmp((char*) ticket->issuer, TICKET_ISSUER_DEV, 0x40) != 0)) ||
|
2020-01-23 00:29:11 +00:00
|
|
|
(ticket->commonkey_idx >= 6) ||
|
|
|
|
(getbe32(&ticket->content_index[0]) != 0x10014) ||
|
|
|
|
(getbe32(&ticket->content_index[4]) < 0x14) ||
|
|
|
|
(getbe32(&ticket->content_index[12]) != 0x10014) ||
|
|
|
|
(getbe32(&ticket->content_index[16]) != 0))
|
2016-12-16 03:34:49 +01:00
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-01-10 02:08:29 +01:00
|
|
|
u32 ValidateTicketSignature(Ticket* ticket) {
|
|
|
|
static bool got_modexp = false;
|
2019-04-17 00:08:43 +02:00
|
|
|
static u32 mod[0x100 / 0x4] = { 0 };
|
2018-01-10 02:08:29 +01:00
|
|
|
static u32 exp = 0;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-10 02:08:29 +01:00
|
|
|
if (!got_modexp) {
|
2018-08-13 00:08:56 +02:00
|
|
|
// grab mod/exp from cert from cert.db
|
|
|
|
if (LoadCertFromCertDb(0x3F10, NULL, mod, &exp) == 0)
|
|
|
|
got_modexp = true;
|
|
|
|
else return 1;
|
2018-01-10 02:08:29 +01:00
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-10 02:08:29 +01:00
|
|
|
if (!RSA_setKey2048(3, mod, exp) ||
|
2020-01-23 00:29:11 +00:00
|
|
|
!RSA_verify2048((void*) &(ticket->signature), (void*) &(ticket->issuer), GetTicketSize(ticket) - 0x140))
|
2018-01-10 02:08:29 +01:00
|
|
|
return 1;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-10 02:08:29 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-12-16 15:26:19 +01:00
|
|
|
u32 BuildFakeTicket(Ticket* ticket, u8* title_id) {
|
2020-07-23 13:46:42 -03:00
|
|
|
static const u8 sig_type[4] = { TICKET_SIG_TYPE }; // RSA_2048 SHA256
|
|
|
|
static const u8 ticket_cnt_index[] = { // whatever this is
|
2016-12-16 15:26:19 +01:00
|
|
|
0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x14,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x84,
|
2020-02-03 22:06:44 +00:00
|
|
|
0x00, 0x00, 0x00, 0x84, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
2016-12-16 15:26:19 +01:00
|
|
|
};
|
|
|
|
// set ticket all zero for a clean start
|
2020-01-23 00:29:11 +00:00
|
|
|
memset(ticket, 0x00, TICKET_COMMON_SIZE); // 0xAC being size of this fake ticket's content index
|
2016-12-16 15:26:19 +01:00
|
|
|
// fill ticket values
|
|
|
|
memcpy(ticket->sig_type, sig_type, 4);
|
|
|
|
memset(ticket->signature, 0xFF, 0x100);
|
2017-02-26 14:04:03 +01:00
|
|
|
snprintf((char*) ticket->issuer, 0x40, IS_DEVKIT ? TICKET_ISSUER_DEV : TICKET_ISSUER);
|
2016-12-16 15:26:19 +01:00
|
|
|
memset(ticket->ecdsa, 0xFF, 0x3C);
|
|
|
|
ticket->version = 0x01;
|
|
|
|
memset(ticket->titlekey, 0xFF, 16);
|
|
|
|
memcpy(ticket->title_id, title_id, 8);
|
|
|
|
ticket->commonkey_idx = 0x00; // eshop
|
|
|
|
ticket->audit = 0x01; // whatever
|
|
|
|
memcpy(ticket->content_index, ticket_cnt_index, sizeof(ticket_cnt_index));
|
2020-02-03 22:06:44 +00:00
|
|
|
memset(&ticket->content_index[sizeof(ticket_cnt_index)], 0xFF, 0x80); // 1024 content indexes
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-12-16 03:34:49 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2017-02-06 03:16:35 +01:00
|
|
|
|
2020-01-23 00:29:11 +00:00
|
|
|
u32 GetTicketContentIndexSize(const Ticket* ticket) {
|
|
|
|
return getbe32(&ticket->content_index[4]);
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 GetTicketSize(const Ticket* ticket) {
|
|
|
|
return sizeof(Ticket) + GetTicketContentIndexSize(ticket);
|
|
|
|
}
|
|
|
|
|
2017-02-06 03:16:35 +01:00
|
|
|
u32 BuildTicketCert(u8* tickcert) {
|
2020-07-23 13:46:42 -03:00
|
|
|
static const u8 cert_hash_expected[0x20] = {
|
2020-08-24 21:27:19 -07:00
|
|
|
0xDC, 0x15, 0x3C, 0x2B, 0x8A, 0x0A, 0xC8, 0x74, 0xA9, 0xDC, 0x78, 0x61, 0x0E, 0x6A, 0x8F, 0xE3,
|
2017-02-06 03:16:35 +01:00
|
|
|
0xE6, 0xB1, 0x34, 0xD5, 0x52, 0x88, 0x73, 0xC9, 0x61, 0xFB, 0xC7, 0x95, 0xCB, 0x47, 0xE6, 0x97
|
|
|
|
};
|
2020-07-23 13:46:42 -03:00
|
|
|
static const u8 cert_hash_expected_dev[0x20] = {
|
2017-02-26 14:04:03 +01:00
|
|
|
0x97, 0x2A, 0x32, 0xFF, 0x9D, 0x4B, 0xAA, 0x2F, 0x1A, 0x24, 0xCF, 0x21, 0x13, 0x87, 0xF5, 0x38,
|
|
|
|
0xC6, 0x4B, 0xD4, 0x8F, 0xDF, 0x13, 0x21, 0x3D, 0xFC, 0x72, 0xFC, 0x8D, 0x9F, 0xDD, 0x01, 0x0E
|
|
|
|
};
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-02-06 03:16:35 +01:00
|
|
|
// open certs.db file on SysNAND
|
|
|
|
FIL db;
|
|
|
|
UINT bytes_read;
|
|
|
|
if (f_open(&db, "1:/dbs/certs.db", FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
|
|
|
return 1;
|
2018-01-10 02:08:29 +01:00
|
|
|
// grab ticket cert from 3 offsets
|
2017-02-06 03:16:35 +01:00
|
|
|
f_lseek(&db, 0x3F10);
|
|
|
|
f_read(&db, tickcert + 0x000, 0x300, &bytes_read);
|
|
|
|
f_lseek(&db, 0x0C10);
|
|
|
|
f_read(&db, tickcert + 0x300, 0x1F0, &bytes_read);
|
|
|
|
f_lseek(&db, 0x3A00);
|
|
|
|
f_read(&db, tickcert + 0x4F0, 0x210, &bytes_read);
|
|
|
|
f_close(&db);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-02-06 03:16:35 +01:00
|
|
|
// check the certificate hash
|
|
|
|
u8 cert_hash[0x20];
|
|
|
|
sha_quick(cert_hash, tickcert, TICKET_CDNCERT_SIZE, SHA256_MODE);
|
2017-02-26 14:04:03 +01:00
|
|
|
if (memcmp(cert_hash, IS_DEVKIT ? cert_hash_expected_dev : cert_hash_expected, 0x20) != 0)
|
2017-02-06 03:16:35 +01:00
|
|
|
return 1;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-02-06 03:16:35 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2020-02-03 16:14:07 +00:00
|
|
|
|
|
|
|
u32 TicketRightsCheck_InitContext(TicketRightsCheck* ctx, Ticket* ticket) {
|
|
|
|
if (!ticket || ValidateTicket(ticket)) return 1;
|
|
|
|
|
|
|
|
const TicketContentIndexMainHeader* mheader = (const TicketContentIndexMainHeader*)&ticket->content_index[0];
|
|
|
|
u32 dheader_pos = getbe32(&mheader->data_header_relative_offset[0]);
|
|
|
|
u32 cindex_size = getbe32(&mheader->content_index_size[0]);
|
|
|
|
|
|
|
|
// data header is not inbounds, so it's not valid for use
|
|
|
|
if (cindex_size < dheader_pos || dheader_pos + sizeof(TicketContentIndexDataHeader) > cindex_size) return 1;
|
|
|
|
|
|
|
|
const TicketContentIndexDataHeader* dheader = (const TicketContentIndexDataHeader*)&ticket->content_index[dheader_pos];
|
|
|
|
u32 data_pos = getbe32(&dheader->data_relative_offset[0]);
|
|
|
|
u32 count = getbe32(&dheader->max_entry_count[0]);
|
|
|
|
u32 data_max_size = cindex_size - data_pos;
|
|
|
|
|
|
|
|
count = min(data_max_size / sizeof(TicketRightsField), count);
|
|
|
|
|
|
|
|
// if no entries or data type isn't what we want or not enough space for at least one entry,
|
|
|
|
// it still is valid, but it will just follow other rules
|
|
|
|
if (count == 0 || getbe16(&dheader->data_type[0]) != 3) {
|
|
|
|
ctx->count = 0;
|
|
|
|
ctx->rights = NULL;
|
|
|
|
} else {
|
|
|
|
ctx->count = count;
|
|
|
|
ctx->rights = (const TicketRightsField*)&ticket->content_index[data_pos];
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool TicketRightsCheck_CheckIndex(TicketRightsCheck* ctx, u16 index) {
|
|
|
|
if (ctx->count == 0) return index < 256; // when no fields, true if below 256
|
|
|
|
|
|
|
|
bool hasright = false;
|
|
|
|
|
|
|
|
// it loops until one of these happens:
|
|
|
|
// - we run out of bit fields
|
|
|
|
// - at the first encounter of an index offset field that's bigger than index
|
|
|
|
// - at the first encounter of a positive indicator of content rights
|
|
|
|
for (u32 i = 0; i < ctx->count; i++) {
|
|
|
|
u16 indexoffset = getbe16(&ctx->rights[i].indexoffset[0]);
|
|
|
|
if (index < indexoffset) break;
|
|
|
|
u16 bitpos = index - indexoffset;
|
|
|
|
if (bitpos >= 1024) continue; // not in this field
|
|
|
|
if (ctx->rights[i].rightsbitfield[bitpos / 8] & (1 << (bitpos % 8))) {
|
|
|
|
hasright = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return hasright;
|
|
|
|
}
|