diff --git a/arm9/source/crypto/rsa.c b/arm9/source/crypto/rsa.c new file mode 100644 index 0000000..5c7bff7 --- /dev/null +++ b/arm9/source/crypto/rsa.c @@ -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 . + */ + +#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; +} diff --git a/arm9/source/crypto/rsa.h b/arm9/source/crypto/rsa.h new file mode 100644 index 0000000..3f230e1 --- /dev/null +++ b/arm9/source/crypto/rsa.h @@ -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 . + */ + +#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); diff --git a/arm9/source/game/ticket.c b/arm9/source/game/ticket.c index aa49343..2cd680e 100644 --- a/arm9/source/game/ticket.c +++ b/arm9/source/game/ticket.c @@ -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); diff --git a/arm9/source/game/ticket.h b/arm9/source/game/ticket.h index 293f56e..b07a1e1 100644 --- a/arm9/source/game/ticket.h +++ b/arm9/source/game/ticket.h @@ -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); diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index 918f068..d0ffc9d 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -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 } } diff --git a/arm9/source/virtual/vtickdb.c b/arm9/source/virtual/vtickdb.c index 565b9f7..44ecb66 100644 --- a/arm9/source/virtual/vtickdb.c +++ b/arm9/source/virtual/vtickdb.c @@ -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); }