159 lines
6.0 KiB
C
Raw Normal View History

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"
#include "sha.h"
#include "rsa.h"
2016-12-16 03:34:49 +01:00
#include "ff.h"
u32 ValidateTicket(Ticket* ticket) {
const u8 magic[] = { TICKET_SIG_TYPE };
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)) ||
(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;
}
u32 ValidateTicketSignature(Ticket* ticket) {
static bool got_modexp = false;
static u32 mod[0x100 / 0x4] = { 0 };
static u32 exp = 0;
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;
}
if (!RSA_setKey2048(3, mod, exp) ||
!RSA_verify2048((void*) &(ticket->signature), (void*) &(ticket->issuer), GetTicketSize(ticket) - 0x140))
return 1;
return 0;
}
2016-12-16 15:26:19 +01:00
u32 BuildFakeTicket(Ticket* ticket, u8* title_id) {
const u8 sig_type[4] = { TICKET_SIG_TYPE }; // RSA_2048 SHA256
const u8 ticket_cnt_index[] = { // whatever this is
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
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
2016-12-16 15:26:19 +01:00
2016-12-16 03:34:49 +01:00
return 0;
}
u32 GetTicketContentIndexSize(const Ticket* ticket) {
return getbe32(&ticket->content_index[4]);
}
u32 GetTicketSize(const Ticket* ticket) {
return sizeof(Ticket) + GetTicketContentIndexSize(ticket);
}
u32 BuildTicketCert(u8* tickcert) {
const u8 cert_hash_expected[0x20] = {
0xDC, 0x15, 0x3C, 0x2B, 0x8A, 0x0A, 0xC8, 0x74, 0xA9, 0xDC, 0x78, 0x61, 0x0E, 0x6A, 0x8F, 0xE3,
0xE6, 0xB1, 0x34, 0xD5, 0x52, 0x88, 0x73, 0xC9, 0x61, 0xFB, 0xC7, 0x95, 0xCB, 0x47, 0xE6, 0x97
};
2017-02-26 14:04:03 +01:00
const u8 cert_hash_expected_dev[0x20] = {
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
};
// 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;
// grab ticket cert from 3 offsets
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);
// 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)
return 1;
return 0;
}
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;
}