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);
}