forked from Mirror/GodMode9
Validate tickets via RSA signature
-> only proper / signed tickets in T:/ drive -> 100% safe way of telling a ticket is legit
This commit is contained in:
parent
5d8758bf83
commit
24c31f482d
119
arm9/source/crypto/rsa.c
Normal file
119
arm9/source/crypto/rsa.c
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* This file is part of fastboot 3DS
|
||||
* Copyright (C) 2017 derrek, profi200
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "rsa.h"
|
||||
#include "sha.h"
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////
|
||||
// RSA //
|
||||
//////////////////////////////////
|
||||
|
||||
#define RSA_REGS_BASE (0x10000000 + 0xB000)
|
||||
#define REG_RSA_CNT *((vu32*)(RSA_REGS_BASE + 0x000))
|
||||
#define REG_RSA_UNK_F0 *((vu32*)(RSA_REGS_BASE + 0x0F0))
|
||||
#define REGs_RSA_SLOT0 ((vu32*)(RSA_REGS_BASE + 0x100))
|
||||
#define REGs_RSA_SLOT1 ((vu32*)(RSA_REGS_BASE + 0x110))
|
||||
#define REGs_RSA_SLOT2 ((vu32*)(RSA_REGS_BASE + 0x120))
|
||||
#define REGs_RSA_SLOT3 ((vu32*)(RSA_REGS_BASE + 0x130))
|
||||
#define rsaSlots ((RsaSlot*)(RSA_REGS_BASE + 0x100))
|
||||
#define REG_RSA_EXP ((vu32*)(RSA_REGS_BASE + 0x200))
|
||||
#define REG_RSA_MOD ( (RSA_REGS_BASE + 0x400))
|
||||
#define REG_RSA_TXT ( (RSA_REGS_BASE + 0x800))
|
||||
|
||||
typedef struct
|
||||
{
|
||||
vu32 REG_RSA_SLOTCNT;
|
||||
vu32 REG_RSA_SLOTSIZE;
|
||||
vu32 REG_RSA_SLOT_UNK_0x8;
|
||||
vu32 REG_RSA_SLOT_UNK_0xC;
|
||||
} RsaSlot;
|
||||
|
||||
|
||||
void RSA_init(void)
|
||||
{
|
||||
REG_RSA_UNK_F0 = 0;
|
||||
}
|
||||
|
||||
static void rsaWaitBusy(void)
|
||||
{
|
||||
while(REG_RSA_CNT & RSA_ENABLE);
|
||||
}
|
||||
|
||||
void RSA_selectKeyslot(u8 keyslot)
|
||||
{
|
||||
rsaWaitBusy();
|
||||
REG_RSA_CNT = (REG_RSA_CNT & ~RSA_KEYSLOT(0xFu)) | RSA_KEYSLOT(keyslot);
|
||||
}
|
||||
|
||||
bool RSA_setKey2048(u8 keyslot, const u8 *const mod, u32 exp)
|
||||
{
|
||||
RsaSlot *slot = &rsaSlots[keyslot];
|
||||
rsaWaitBusy();
|
||||
if(slot->REG_RSA_SLOTCNT & RSA_KEY_WR_PROT) return false;
|
||||
// Unset key if bit 31 is not set. No idea why but boot9 does this.
|
||||
if(!(slot->REG_RSA_SLOTCNT & RSA_KEY_UNK_BIT31)) slot->REG_RSA_SLOTCNT &= ~RSA_KEY_STAT_SET;
|
||||
|
||||
REG_RSA_CNT = RSA_INPUT_NORMAL | RSA_INPUT_BIG | RSA_KEYSLOT(keyslot);
|
||||
memset((void*)REG_RSA_EXP, 0, 0x100 - 4);
|
||||
REG_RSA_EXP[(0x100>>2) - 1] = exp;
|
||||
|
||||
if(slot->REG_RSA_SLOTSIZE != RSA_SLOTSIZE_2048) return false;
|
||||
memcpy((void*)REG_RSA_MOD, mod, 0x100);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RSA_decrypt2048(void *const decSig, const void *const encSig)
|
||||
{
|
||||
const u8 keyslot = RSA_GET_KEYSLOT;
|
||||
rsaWaitBusy();
|
||||
if(!(rsaSlots[keyslot].REG_RSA_SLOTCNT & RSA_KEY_STAT_SET)) return false;
|
||||
|
||||
REG_RSA_CNT |= RSA_INPUT_NORMAL | RSA_INPUT_BIG;
|
||||
memcpy((void*)REG_RSA_TXT, encSig, 0x100);
|
||||
|
||||
REG_RSA_CNT |= RSA_ENABLE;
|
||||
rsaWaitBusy();
|
||||
memcpy(decSig, (void*)REG_RSA_TXT, 0x100);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RSA_verify2048(const u32 *const encSig, const u32 *const data, u32 size)
|
||||
{
|
||||
u8 decSig[0x100];
|
||||
if(!RSA_decrypt2048(decSig, encSig)) return false;
|
||||
|
||||
if(decSig[0] != 0x00 || decSig[1] != 0x01) return false;
|
||||
|
||||
u32 read = 2;
|
||||
while(read < 0x100)
|
||||
{
|
||||
if(decSig[read] != 0xFF) break;
|
||||
read++;
|
||||
}
|
||||
if(read != 0xCC || decSig[read] != 0x00) return false;
|
||||
|
||||
// ASN.1 is a clusterfuck so we skip parsing the remaining headers
|
||||
// and hardcode the hash location.
|
||||
|
||||
return sha_cmp(&(decSig[0xE0]), data, size, SHA256_MODE) == 0;
|
||||
}
|
91
arm9/source/crypto/rsa.h
Normal file
91
arm9/source/crypto/rsa.h
Normal file
@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* This file is part of fastboot 3DS
|
||||
* Copyright (C) 2017 derrek, profi200
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////
|
||||
// RSA //
|
||||
//////////////////////////////////
|
||||
|
||||
// REG_RSA_CNT
|
||||
#define RSA_ENABLE (1u)
|
||||
#define RSA_UNK_BIT1 (1u<<1)
|
||||
#define RSA_KEYSLOT(k) ((k)<<4)
|
||||
#define RSA_GET_KEYSLOT ((REG_RSA_CNT & RSA_KEYSLOT(0xFu))>>4)
|
||||
#define RSA_INPUT_BIG (1u<<8)
|
||||
#define RSA_INPUT_LITTLE (0u)
|
||||
#define RSA_INPUT_NORMAL (1u<<9)
|
||||
#define RSA_INPUT_REVERSED (0u)
|
||||
|
||||
// RSA_SLOTCNT
|
||||
#define RSA_KEY_STAT_SET (1u)
|
||||
#define RSA_KEY_WR_PROT (1u<<1)
|
||||
#define RSA_KEY_UNK_BIT31 (1u<<31)
|
||||
|
||||
// RSA_SLOTSIZE
|
||||
#define RSA_SLOTSIZE_2048 (0x40u)
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initializes the RSA hardware.
|
||||
*/
|
||||
void RSA_init(void);
|
||||
|
||||
/**
|
||||
* @brief Selects the given keyslot for all following RSA operations.
|
||||
*
|
||||
* @param[in] keyslot The keyslot to select.
|
||||
*/
|
||||
void RSA_selectKeyslot(u8 keyslot);
|
||||
|
||||
/**
|
||||
* @brief Sets a RSA modulus + exponent in the specified keyslot.
|
||||
*
|
||||
* @param[in] keyslot The keyslot this key will be set for.
|
||||
* @param[in] mod Pointer to 2048-bit RSA modulus data.
|
||||
* @param[in] exp The exponent to set.
|
||||
*
|
||||
* @return Returns true on success, false otherwise.
|
||||
*/
|
||||
bool RSA_setKey2048(u8 keyslot, const u8 *const mod, u32 exp);
|
||||
|
||||
/**
|
||||
* @brief Decrypts a RSA 2048 signature.
|
||||
*
|
||||
* @param decSig Pointer to decrypted destination signature.
|
||||
* @param[in] encSig Pointer to encrypted source signature.
|
||||
*
|
||||
* @return Returns true on success, false otherwise.
|
||||
*/
|
||||
bool RSA_decrypt2048(void *const decSig, const void *const encSig);
|
||||
|
||||
/**
|
||||
* @brief Verifies a RSA 2048 SHA 256 signature.
|
||||
* @brief Note: This function skips the ASN.1 data and is therefore not safe.
|
||||
*
|
||||
* @param[in] encSig Pointer to encrypted source signature.
|
||||
* @param[in] data Pointer to the data to hash.
|
||||
* @param[in] size The hash data size.
|
||||
*
|
||||
* @return Returns true if the signature is valid, false otherwise.
|
||||
*/
|
||||
bool RSA_verify2048(const u32 *const encSig, const u32 *const data, u32 size);
|
@ -3,6 +3,7 @@
|
||||
#include "unittype.h"
|
||||
#include "aes.h"
|
||||
#include "sha.h"
|
||||
#include "rsa.h"
|
||||
#include "ff.h"
|
||||
|
||||
u32 ValidateTicket(Ticket* ticket) {
|
||||
@ -15,6 +16,33 @@ u32 ValidateTicket(Ticket* ticket) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 ValidateTicketSignature(Ticket* ticket) {
|
||||
static bool got_modexp = false;
|
||||
static u8 mod[0x100] = { 0 };
|
||||
static u32 exp = 0;
|
||||
|
||||
// grab cert from cert.db
|
||||
if (!got_modexp) {
|
||||
Certificate cert;
|
||||
FIL db;
|
||||
UINT bytes_read;
|
||||
if (f_open(&db, "1:/dbs/certs.db", FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
||||
return 1;
|
||||
f_lseek(&db, 0x3F10);
|
||||
f_read(&db, &cert, CERT_SIZE, &bytes_read);
|
||||
f_close(&db);
|
||||
memcpy(mod, cert.mod, 0x100);
|
||||
exp = getle32(cert.exp);
|
||||
got_modexp = true;
|
||||
}
|
||||
|
||||
if (!RSA_setKey2048(3, mod, exp) ||
|
||||
!RSA_verify2048((void*) &(ticket->signature), (void*) &(ticket->issuer), 0x210))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 CryptTitleKey(TitleKeyEntry* tik, bool encrypt, bool devkit) {
|
||||
// From https://github.com/profi200/Project_CTR/blob/master/makerom/pki/prod.h#L19
|
||||
static const u8 common_keyy[6][16] __attribute__((aligned(16))) = {
|
||||
@ -68,7 +96,7 @@ Ticket* TicketFromTickDbChunk(u8* chunk, u8* title_id, bool legit_pls) {
|
||||
if ((getle32(chunk + 0x10) == 0) || (getle32(chunk + 0x14) != sizeof(Ticket))) return NULL;
|
||||
if (ValidateTicket(tick) != 0) return NULL; // ticket not validated
|
||||
if (title_id && (memcmp(title_id, tick->title_id, 8) != 0)) return NULL; // title id not matching
|
||||
if (legit_pls && (getbe64(tick->ticket_id) == 0)) return NULL; // legit check, not perfect
|
||||
if (legit_pls && (ValidateTicketSignature(tick) != 0)) return NULL; // legit check using RSA sig
|
||||
|
||||
return tick;
|
||||
}
|
||||
@ -195,7 +223,7 @@ u32 BuildTicketCert(u8* tickcert) {
|
||||
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
|
||||
// grab ticket cert from 3 offsets
|
||||
f_lseek(&db, 0x3F10);
|
||||
f_read(&db, tickcert + 0x000, 0x300, &bytes_read);
|
||||
f_lseek(&db, 0x0C10);
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "common.h"
|
||||
|
||||
#define TICKET_SIZE sizeof(Ticket)
|
||||
#define CERT_SIZE sizeof(Certificate)
|
||||
#define TICKET_CDNCERT_SIZE 0x700
|
||||
|
||||
#define TICKET_ISSUER "Root-CA00000003-XS0000000c"
|
||||
@ -27,6 +28,7 @@
|
||||
0x00, 0xEE, 0x37, 0x02, 0x00, 0x00, 0x00, 0x00
|
||||
|
||||
// from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tik.h#L15-L39
|
||||
// all numbers in big endian
|
||||
typedef struct {
|
||||
u8 sig_type[4];
|
||||
u8 signature[0x100];
|
||||
@ -57,6 +59,21 @@ typedef struct {
|
||||
u8 content_index[0xAC];
|
||||
} __attribute__((packed)) Ticket;
|
||||
|
||||
// from: http://3dbrew.org/wiki/Certificates
|
||||
// all numbers in big endian
|
||||
typedef struct {
|
||||
u8 sig_type[4]; // expected: 0x010004 / RSA_2048 SHA256
|
||||
u8 signature[0x100];
|
||||
u8 padding0[0x3C];
|
||||
u8 issuer[0x40];
|
||||
u8 keytype[4]; // expected: 0x01 / RSA_2048
|
||||
u8 name[0x40];
|
||||
u8 unknown[4];
|
||||
u8 mod[0x100];
|
||||
u8 exp[0x04];
|
||||
u8 padding1[0x34];
|
||||
} __attribute__((packed)) Certificate;
|
||||
|
||||
typedef struct {
|
||||
u32 commonkey_idx;
|
||||
u8 reserved[4];
|
||||
@ -71,6 +88,7 @@ typedef struct {
|
||||
} __attribute__((packed)) TitleKeysInfo;
|
||||
|
||||
u32 ValidateTicket(Ticket* ticket);
|
||||
u32 ValidateTicketSignature(Ticket* ticket);
|
||||
u32 GetTitleKey(u8* titlekey, Ticket* ticket);
|
||||
Ticket* TicketFromTickDbChunk(u8* chunk, u8* title_id, bool legit_pls);
|
||||
u32 FindTicket(Ticket* ticket, u8* title_id, bool force_legit, bool emunand);
|
||||
|
@ -1784,7 +1784,7 @@ u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) {
|
||||
return 1;
|
||||
}
|
||||
for (; data + TICKET_SIZE < ((u8*) TEMP_BUFFER) + read_bytes; data += 0x200) {
|
||||
Ticket* ticket = TicketFromTickDbChunk(data, NULL, false);
|
||||
Ticket* ticket = TicketFromTickDbChunk(data, NULL, true);
|
||||
if (!ticket || (ticket->commonkey_idx >= 2) || !getbe64(ticket->ticket_id)) continue;
|
||||
if (TIKDB_SIZE(tik_info) + 32 > MAIN_BUFFER_SIZE) return 1;
|
||||
AddTicketToInfo(tik_info, ticket, dec); // ignore result
|
||||
@ -1801,8 +1801,7 @@ u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) {
|
||||
TitleKeyEntry* tik = tik_info_merge->entries;
|
||||
for (u32 i = 0; i < n_entries; i++, tik++) {
|
||||
if (TIKDB_SIZE(tik_info) + 32 > MAIN_BUFFER_SIZE) return 1;
|
||||
AddTitleKeyToInfo(tik_info, tik, !(filetype & FLAG_ENC), dec, false); // ignore result
|
||||
|
||||
AddTitleKeyToInfo(tik_info, tik, !(filetype & FLAG_ENC), dec, false); // ignore result
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ u32 InitVTickDbDrive(void) { // prerequisite: ticket.db mounted as image
|
||||
return 0;
|
||||
}
|
||||
for (; data + TICKET_SIZE < ((u8*) TEMP_BUFFER) + read_bytes; data += 0x200) {
|
||||
Ticket* ticket = TicketFromTickDbChunk(data, NULL, false);
|
||||
Ticket* ticket = TicketFromTickDbChunk(data, NULL, true);
|
||||
if (!ticket) continue;
|
||||
AddTickDbInfo(tick_info, ticket, offset_area + i + (data - ((u8*) TEMP_BUFFER)) + 0x18);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user