mirror of
https://github.com/Jimmy-Z/twlnf.git
synced 2025-06-18 18:45:36 -04:00
TMD install WIP
This commit is contained in:
parent
bdae598e23
commit
0a1ca12702
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,6 +2,8 @@
|
||||
*.nds
|
||||
arm7/build
|
||||
arm9/build
|
||||
arm9/term256
|
||||
test scripts
|
||||
.*
|
||||
*.sln
|
||||
*.vcxproj*
|
||||
|
7
arm9/mbedtls/readme.txt
Normal file
7
arm9/mbedtls/readme.txt
Normal file
@ -0,0 +1,7 @@
|
||||
aes.c/.h rsa.c/.h are heavily modified/reduced
|
||||
|
||||
bignum.c/.h bn_mul.h only had some minor modifications:
|
||||
headers location moved from mbedtls/ to .
|
||||
disabled some unused functions by "#if 0 // unused"
|
||||
ASCII I/O
|
||||
everything below mbedtls_mpi_exp_mod
|
@ -1,8 +0,0 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
@ -114,16 +114,19 @@ static void dsi_aes_set_key(u32 *rk, const u32 *console_id, key_mode_t mode) {
|
||||
aes_set_key_enc_128_be(rk, (u8*)key);
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define DTCM_BSS
|
||||
#endif
|
||||
|
||||
void dsi_sha1(void *digest, const void *data, unsigned len) {
|
||||
swiSHA1context_t ctx;
|
||||
ctx.sha_block = 0;
|
||||
swiSHA1Init(&ctx);
|
||||
swiSHA1Update(&ctx, data, len);
|
||||
swiSHA1Final(digest, &ctx);
|
||||
int dsi_sha1_verify(const void *digest_verify, const void *data, unsigned len) {
|
||||
u8 digest[SHA1_LEN];
|
||||
swiSHA1Calc(digest, data, len);
|
||||
// return type of swiSHA1Verify() is declared void, so how exactly should we use it?
|
||||
int ret = memcmp(digest, digest_verify, SHA1_LEN);
|
||||
if (ret != 0) {
|
||||
prt(" ");
|
||||
print_bytes(digest_verify, SHA1_LEN);
|
||||
prt("\n ");
|
||||
print_bytes(digest, SHA1_LEN);
|
||||
prt("\n");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u32 nand_ctr_rk[RK_LEN];
|
||||
@ -145,8 +148,8 @@ void dsi_crypt_init(const u8 *console_id_be, const u8 *emmc_cid, int is3DS) {
|
||||
dsi_aes_set_key(nand_ctr_rk, console_id, is3DS ? NAND_3DS : NAND);
|
||||
dsi_aes_set_key(es_rk, console_id, ES);
|
||||
|
||||
u32 digest[5];
|
||||
dsi_sha1(digest, emmc_cid, 16);
|
||||
u32 digest[SHA1_LEN / sizeof(u32)];
|
||||
swiSHA1Calc(digest, emmc_cid, 16);
|
||||
nand_ctr_iv[0] = digest[0];
|
||||
nand_ctr_iv[1] = digest[1];
|
||||
nand_ctr_iv[2] = digest[2];
|
||||
@ -222,7 +225,7 @@ int dsi_es_block_crypt(u8 *buf, unsigned buf_len, crypt_mode_t mode) {
|
||||
(unsigned)block_size, (unsigned)(buf_len - sizeof(es_block_footer_t)));
|
||||
return 1;
|
||||
}
|
||||
// padd to multiple of 16
|
||||
// padding to multiple of 16
|
||||
u32 remainder = block_size & 0xf;
|
||||
if (remainder != 0) {
|
||||
zero(pad32);
|
||||
@ -264,6 +267,7 @@ int dsi_es_block_crypt(u8 *buf, unsigned buf_len, crypt_mode_t mode) {
|
||||
add_128_32(ctr32, 1);
|
||||
}
|
||||
}
|
||||
// AES-CCM MAC final
|
||||
xor_128(mac32, mac32, pad32);
|
||||
if (mode == DECRYPT) {
|
||||
if (memcmp(mac, ccm_mac, 16) == 0) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#define SHA1_LEN 20
|
||||
|
||||
#define AES_BLOCK_SIZE 16
|
||||
|
||||
@ -15,7 +15,7 @@ typedef enum {
|
||||
ES
|
||||
} key_mode_t;
|
||||
|
||||
void dsi_sha1(void *digest, const void *data, unsigned len);
|
||||
int dsi_sha1_verify(const void *digest_verify, const void *data, unsigned len);
|
||||
|
||||
void dsi_crypt_init(const u8 *console_id_be, const u8 *emmc_cid, int is3DS);
|
||||
|
||||
|
@ -16,8 +16,6 @@
|
||||
#include "crypto.h"
|
||||
#include "tmd.h"
|
||||
|
||||
#define SHA1_LEN 20
|
||||
|
||||
#define RESERVE_FREE (5 * 1024 * 1024)
|
||||
|
||||
term_t t0;
|
||||
@ -170,15 +168,6 @@ void menu_move(int move) {
|
||||
}
|
||||
}
|
||||
|
||||
void exit_with_prompt(int exit_code) {
|
||||
prt("press A to exit...");
|
||||
while (1) {
|
||||
swiWaitForVBlank();
|
||||
scanKeys();
|
||||
if (keysDown() & KEY_A) break;
|
||||
}
|
||||
exit(exit_code);
|
||||
}
|
||||
|
||||
unsigned wait_keys(unsigned keys) {
|
||||
while (1) {
|
||||
@ -191,6 +180,23 @@ unsigned wait_keys(unsigned keys) {
|
||||
}
|
||||
}
|
||||
|
||||
void exit_with_prompt(int exit_code) {
|
||||
prt("press A to exit...");
|
||||
wait_keys(KEY_A);
|
||||
exit(exit_code);
|
||||
}
|
||||
|
||||
int wait_yes_no(const char* msg) {
|
||||
prt(msg);
|
||||
prt(" Yes(A)/No(B)\n");
|
||||
if (wait_keys(KEY_A | KEY_B) == KEY_A) {
|
||||
return 1;
|
||||
} else {
|
||||
prt("cancelled\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void walk_cb_lst_file(const char *name, size_t size, void *p_param) {
|
||||
if (size == INVALID_SIZE) {
|
||||
return;
|
||||
@ -306,70 +312,15 @@ void menu_action_script(const char *name, const char *full_path) {
|
||||
prt("insufficient NAND space\n");
|
||||
return;
|
||||
}
|
||||
prt("execute? Yes(A)/No(B)\n");
|
||||
if(wait_keys(KEY_A | KEY_B) & KEY_A) {
|
||||
if(wait_yes_no("execute?")){
|
||||
ret = scripting(full_path, 0, 0);
|
||||
// TODO: some scripts might not induce writes
|
||||
++executions;
|
||||
iprtf("execution returned %d\n", ret);
|
||||
// maybe we should prompt to restore a NAND image
|
||||
} else {
|
||||
prt("cancelled\n");
|
||||
}
|
||||
}
|
||||
|
||||
void menu_action_tmd(const char *name, const char *full_path) {
|
||||
// TMD file
|
||||
u8 *tmd_buf = malloc(sizeof(tmd_header_v0_t) + sizeof(tmd_content_v0_t));
|
||||
if (tmd_buf == 0) {
|
||||
prt("failed to alloc memory for TMD\n");
|
||||
return;
|
||||
}
|
||||
if (load_block_from_file(tmd_buf, full_path, 0,
|
||||
sizeof(tmd_header_v0_t) + sizeof(tmd_content_v0_t)) != 0) {
|
||||
prt("failed to load TMD\n");
|
||||
free(tmd_buf);
|
||||
return;
|
||||
}
|
||||
tmd_header_v0_t *header = (tmd_header_v0_t*)tmd_buf;
|
||||
u32 title_id[2];
|
||||
GET_UINT32_BE(title_id[0], header->title_id, 4);
|
||||
GET_UINT32_BE(title_id[1], header->title_id, 0);
|
||||
if (title_id[1] != dsiware_title_id_h) {
|
||||
iprtf("not a DSiWare title(%08lx)\n", title_id[1]);
|
||||
free(tmd_buf);
|
||||
return;
|
||||
}
|
||||
iprtf("Title ID: %08lx%08lx\n", title_id[1], title_id[0]);
|
||||
if (header->num_content[0] != 0 || header->num_content[1] != 1) {
|
||||
iprtf("num_content should to be 1(%02x%02x)\n",
|
||||
header->num_content[0], header->num_content[1]);
|
||||
free(tmd_buf);
|
||||
return;
|
||||
}
|
||||
unsigned char sig_sha1[SHA1_LEN];
|
||||
if (decrypt_cp07_signature(sig_sha1, header->sig) != 0) {
|
||||
free(tmd_buf);
|
||||
return;
|
||||
}
|
||||
unsigned char sha1[SHA1_LEN];
|
||||
#define SIG_OFFSET (sizeof(header->sig_type) + sizeof(header->sig) + sizeof(header->padding0))
|
||||
#define SIG_LEN (sizeof(tmd_header_v0_t) + sizeof(tmd_content_v0_t) - SIG_OFFSET)
|
||||
dsi_sha1(sha1, tmd_buf + SIG_OFFSET, SIG_LEN);
|
||||
if (memcmp(sha1, sig_sha1, SHA1_LEN) != 0) {
|
||||
prt("signature verification failed\n");
|
||||
free(tmd_buf);
|
||||
return;
|
||||
} else {
|
||||
prt("signature verified\n");
|
||||
}
|
||||
#undef SIG_OFFSET
|
||||
#undef SIG_LEN
|
||||
// tmd_content_v0_t *content = (tmd_content_v0_t*)(tmd_buf + sizeof(tmd_header_v0_t));
|
||||
// TODO
|
||||
free(tmd_buf);
|
||||
}
|
||||
|
||||
static inline int name_is_tmd(const char *name, int len_name) {
|
||||
return (len_name == 3 && strcmp(name, "tmd") == 0)
|
||||
|| (len_name >= 4 && strcmp(name + len_name - 4, ".tmd") == 0);
|
||||
@ -382,17 +333,17 @@ void menu_action(const char *name) {
|
||||
prt("max path length exceeded\n");
|
||||
return;
|
||||
}
|
||||
char *full_path = alloc_buf();
|
||||
strcpy(full_path, browse_path);
|
||||
strcpy(full_path + len_path, name);
|
||||
char *fullname = alloc_buf();
|
||||
strcpy(fullname, browse_path);
|
||||
strcpy(fullname + len_path, name);
|
||||
if (len_name >= 4 && strcmp(name + len_name - 4, ".nfs") == 0) {
|
||||
menu_action_script(name, full_path);
|
||||
menu_action_script(name, fullname);
|
||||
}else if(cert_ready && ticket_ready && name_is_tmd(name, len_name)){
|
||||
menu_action_tmd(name, full_path);
|
||||
install_tmd(fullname, browse_path, df(nand_root, 0) - RESERVE_FREE);
|
||||
}else{
|
||||
prt("don't know how to handle this file\n");
|
||||
}
|
||||
free_buf(full_path);
|
||||
free_buf(fullname);
|
||||
}
|
||||
|
||||
void menu() {
|
||||
@ -412,11 +363,8 @@ void menu() {
|
||||
uint32 keys = keysDown();
|
||||
int needs_redraw = 0;
|
||||
if (keys & KEY_SELECT) {
|
||||
prt("unmount and quit(A)? cancel(B)\n");
|
||||
if (wait_keys(KEY_A | KEY_B) & KEY_A) {
|
||||
if (wait_yes_no("unmount and quit?")) {
|
||||
break;
|
||||
} else {
|
||||
prt("cancelled\n");
|
||||
}
|
||||
} else if (keys & (KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT)) {
|
||||
if (keys & KEY_UP) {
|
||||
@ -525,7 +473,7 @@ int main(int argc, const char * const argv[]) {
|
||||
|
||||
if(heap_init() != 0 || scripting_init() != 0){
|
||||
prt("failed to alloc memory\n");
|
||||
return -1;
|
||||
exit_with_prompt(-1);
|
||||
}
|
||||
|
||||
u32 bat_reg = getBatteryLevel();
|
||||
@ -614,8 +562,7 @@ int main(int argc, const char * const argv[]) {
|
||||
}
|
||||
// TODO: also test against sha1
|
||||
if ((ret = test_image_against_nand()) != 0) {
|
||||
prt("you don't have a valid NAND backup, backup now? Yes(A)/No(B)\n");
|
||||
if (wait_keys(KEY_A | KEY_B) & KEY_A) {
|
||||
if (wait_yes_no("you don't have a valid NAND backup, backup now?")) {
|
||||
if ((ret = backup()) != 0) {
|
||||
prt("backup failed\n");
|
||||
exit_with_prompt(ret);
|
||||
@ -631,7 +578,7 @@ int main(int argc, const char * const argv[]) {
|
||||
return 0;
|
||||
} else if(keys & KEY_A){
|
||||
mode = MODE_IMAGE;
|
||||
} else if (keys & KEY_X) {
|
||||
} else if (keys == KEY_X) {
|
||||
mode = MODE_DIRECT;
|
||||
prt(Red "you are mounting NAND R/W DIRECTLY, EXERCISE EXTREME CAUTION\n");
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include <fat.h>
|
||||
#include "../mbedtls/aes.h"
|
||||
#include "../term256/term256ext.h"
|
||||
#include "common.h"
|
||||
#include "utils.h"
|
||||
#include "crypto.h"
|
||||
#include "sector0.h"
|
||||
|
@ -7,14 +7,11 @@
|
||||
#include <sys/stat.h>
|
||||
#include "heap.h"
|
||||
#include "../term256/term256ext.h"
|
||||
#include "common.h"
|
||||
#include "utils.h"
|
||||
#include "scripting.h"
|
||||
|
||||
extern const char nand_root[];
|
||||
|
||||
extern swiSHA1context_t sha1ctx;
|
||||
|
||||
#define FILE_BUF_LEN (128 << 10)
|
||||
static u8* file_buf = 0;
|
||||
|
||||
@ -260,6 +257,7 @@ int sha1_file(void *digest, const char *name) {
|
||||
if (f == 0) {
|
||||
return -1;
|
||||
}
|
||||
swiSHA1context_t sha1ctx;
|
||||
sha1ctx.sha_block = 0;
|
||||
swiSHA1Init(&sha1ctx);
|
||||
int size = 0;
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include "../term256/term256ext.h"
|
||||
|
||||
// return 0 for valid NCSD header
|
||||
int parse_ncsd(const u8 sector0[SECTOR_SIZE], int verbose) {
|
||||
int parse_ncsd(const uint8_t sector0[SECTOR_SIZE], int verbose) {
|
||||
const ncsd_header_t * h = (ncsd_header_t *)sector0;
|
||||
if (h->magic == 0x4453434e) {
|
||||
if (verbose) {
|
||||
@ -71,7 +71,7 @@ const mbr_partition_t ptable_3DS[MBR_PARTITIONS] = {
|
||||
};
|
||||
|
||||
// return 0 for valid MBR
|
||||
int parse_mbr(const u8 sector0[SECTOR_SIZE], int is3DS, int verbose) {
|
||||
int parse_mbr(const uint8_t sector0[SECTOR_SIZE], int is3DS, int verbose) {
|
||||
const mbr_t *m = (mbr_t*)sector0;
|
||||
const mbr_partition_t *ref_ptable; // reference partition table
|
||||
int ret = 0;
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include "common.h"
|
||||
|
||||
// https://3dbrew.org/wiki/NCSD#NCSD_header
|
||||
|
||||
@ -17,34 +17,34 @@
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
u32 offset;
|
||||
u32 length;
|
||||
uint32_t offset;
|
||||
uint32_t length;
|
||||
} __PACKED ncsd_partition_t;
|
||||
|
||||
typedef struct {
|
||||
u8 signature[0x100];
|
||||
u32 magic;
|
||||
u32 size;
|
||||
u32 media_id_l;
|
||||
u32 media_id_h;
|
||||
u8 fs_types[NCSD_PARTITIONS];
|
||||
u8 crypt_types[NCSD_PARTITIONS];
|
||||
uint8_t signature[0x100];
|
||||
uint32_t magic;
|
||||
uint32_t size;
|
||||
uint32_t media_id_l;
|
||||
uint32_t media_id_h;
|
||||
uint8_t fs_types[NCSD_PARTITIONS];
|
||||
uint8_t crypt_types[NCSD_PARTITIONS];
|
||||
ncsd_partition_t partitions[NCSD_PARTITIONS];
|
||||
} __PACKED ncsd_header_t;
|
||||
|
||||
typedef struct {
|
||||
u8 head;
|
||||
u8 sector;
|
||||
u8 cylinder;
|
||||
uint8_t head;
|
||||
uint8_t sector;
|
||||
uint8_t cylinder;
|
||||
} __PACKED chs_t;
|
||||
|
||||
typedef struct {
|
||||
u8 status;
|
||||
uint8_t status;
|
||||
chs_t chs_first;
|
||||
u8 type;
|
||||
uint8_t type;
|
||||
chs_t chs_last;
|
||||
u32 offset;
|
||||
u32 length;
|
||||
uint32_t offset;
|
||||
uint32_t length;
|
||||
} __PACKED mbr_partition_t;
|
||||
|
||||
#define MBR_PARTITIONS 4
|
||||
@ -52,10 +52,10 @@ typedef struct {
|
||||
#define MBR_BOOTSTRAP_SIZE 0x1be
|
||||
|
||||
typedef struct {
|
||||
u8 bootstrap[MBR_BOOTSTRAP_SIZE];
|
||||
uint8_t bootstrap[MBR_BOOTSTRAP_SIZE];
|
||||
mbr_partition_t partitions[MBR_PARTITIONS];
|
||||
u8 boot_signature_0;
|
||||
u8 boot_signature_1;
|
||||
uint8_t boot_signature_0;
|
||||
uint8_t boot_signature_1;
|
||||
} __PACKED mbr_t;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
@ -67,6 +67,6 @@ typedef struct {
|
||||
static_assert(sizeof(ncsd_header_t) == 0x160, "sizeof(ncsd_header_t) should equal 0x160");
|
||||
static_assert(sizeof(mbr_t) == SECTOR_SIZE, "sizeof(mbr_t) should equal 0x200");
|
||||
|
||||
int parse_ncsd(const u8 sector0[SECTOR_SIZE], int verbose);
|
||||
int parse_ncsd(const uint8_t sector0[SECTOR_SIZE], int verbose);
|
||||
|
||||
int parse_mbr(const u8 sector0[SECTOR_SIZE], int is3DS, int verbose);
|
||||
int parse_mbr(const uint8_t sector0[SECTOR_SIZE], int is3DS, int verbose);
|
||||
|
@ -9,18 +9,26 @@
|
||||
#include "walk.h"
|
||||
#include "ticket0.h"
|
||||
#include "crypto.h"
|
||||
#include "scripting.h"
|
||||
|
||||
extern const char nand_root[];
|
||||
|
||||
const char cert_sys_path[] = "sys/cert.sys";
|
||||
const char cert_sys_fullname[] = "sys/cert.sys";
|
||||
const char cert_cp07_name[] = "CP00000007";
|
||||
const char dsiware_ticket_path[] = "ticket/00030004/";
|
||||
const uint32_t dsiware_title_id_h = 0x00030004;
|
||||
|
||||
const u32 dsiware_title_id_h = 0x00030004;
|
||||
// used while looking for a ticket template
|
||||
const char ticket_dir_fmt[] = "%sticket/%08lx/";
|
||||
// used while reading app aside tmd on SD
|
||||
const char app_src_fmt[] = "%s%08lx.app";
|
||||
// dst full name generation
|
||||
const char ticket_fullname_fmt[] = "%sticket/%08lx/%08lx.tik";
|
||||
const char tmd_fullname_fmt[] = "%stitle/%08lx/%08lx/content/tmd";
|
||||
const char app_fullname_fmt[] = "%stitle/%08lx/%08lx/content/%08lx.app";
|
||||
|
||||
rsa_context_t rsa_cp07;
|
||||
|
||||
unsigned char * ticket_template;
|
||||
uint8_t * ticket_template;
|
||||
|
||||
int setup_cp07_pubkey() {
|
||||
cert_t *cert = malloc(sizeof(cert_t));
|
||||
@ -31,9 +39,9 @@ int setup_cp07_pubkey() {
|
||||
int ret;
|
||||
char *cert_full_path = alloc_buf();
|
||||
strcpy(cert_full_path, nand_root);
|
||||
strcat(cert_full_path, cert_sys_path);
|
||||
strcat(cert_full_path, cert_sys_fullname);
|
||||
if (load_block_from_file(cert, cert_full_path, 0x700, sizeof(cert_t)) != 0) {
|
||||
iprtf("failed to load cert from %s\n", cert_sys_path);
|
||||
iprtf("failed to load cert from %s\n", cert_sys_fullname);
|
||||
ret = -1;
|
||||
} else {
|
||||
if (strncmp(cert->key_name, cert_cp07_name, sizeof(cert_cp07_name)) != 0) {
|
||||
@ -57,9 +65,9 @@ int setup_cp07_pubkey() {
|
||||
}
|
||||
|
||||
// returns 0 on success, and write SHA1 to out
|
||||
int decrypt_cp07_signature(unsigned char *out, const unsigned char *in) {
|
||||
int decrypt_cp07_signature(uint8_t *out, const uint8_t *in) {
|
||||
static_assert(RSA_2048_LEN <= BUF_SIZE, "BUF_SIZE shouldn't < RSA_2048_LEN");
|
||||
unsigned char *sig = (unsigned char*)alloc_buf();
|
||||
uint8_t *sig = (uint8_t*)alloc_buf();
|
||||
int ret;
|
||||
if (sig == 0) {
|
||||
prt("failed to alloc memory\n");
|
||||
@ -68,7 +76,7 @@ int decrypt_cp07_signature(unsigned char *out, const unsigned char *in) {
|
||||
prt("failed to decrypt signature\n");
|
||||
ret = -2;
|
||||
} else if (sig[0] != 0 || sig[1] != 1 || sig[2] != 0xff
|
||||
|| sig[RSA_2048_LEN - 0x14 - 1] != 0x14) {
|
||||
|| sig[RSA_2048_LEN - SHA1_LEN - 1] != SHA1_LEN) {
|
||||
prt("invalid signature, first 16 bytes:\n\t");
|
||||
print_bytes(sig, 16);
|
||||
prt("\nlast 32 bytes:\n\t");
|
||||
@ -78,7 +86,7 @@ int decrypt_cp07_signature(unsigned char *out, const unsigned char *in) {
|
||||
prt("\n");
|
||||
ret = -3;
|
||||
} else {
|
||||
memcpy(out, sig + RSA_2048_LEN - 0x14, 0x14);
|
||||
memcpy(out, sig + RSA_2048_LEN - SHA1_LEN, SHA1_LEN);
|
||||
ret = 0;
|
||||
}
|
||||
free_buf(sig);
|
||||
@ -100,15 +108,34 @@ static int find_ticket_cb(const char* filename, size_t size, void *cb_param) {
|
||||
// return 0 to let list_dir continue
|
||||
return 0;
|
||||
}
|
||||
#define ES_ENCRYPT_TEST 1
|
||||
#if ES_ENCRYPT_TEST
|
||||
uint8_t *ticket_original = memalign(TICKET_ALIGN, TICKET_SIZE);
|
||||
assert(ticket_original != 0);
|
||||
memcpy(ticket_original, ticket_template, TICKET_SIZE);
|
||||
#endif
|
||||
if (dsi_es_block_crypt(ticket_template, TICKET_SIZE, DECRYPT) != 0) {
|
||||
iprtf("failed to decrypt ticket: %s\n", filename);
|
||||
return 0;
|
||||
}
|
||||
#if ES_ENCRYPT_TEST
|
||||
uint8_t *ticket_enc = memalign(TICKET_ALIGN, TICKET_SIZE);
|
||||
assert(ticket_enc != 0);
|
||||
memcpy(ticket_enc, ticket_template, TICKET_SIZE);
|
||||
assert(dsi_es_block_crypt(ticket_enc, TICKET_SIZE, ENCRYPT) == 0);
|
||||
if (memcmp(ticket_original, ticket_enc, TICKET_SIZE) == 0) {
|
||||
prtf("ES encryption test OK\n");
|
||||
} else {
|
||||
prtf("ES encryption test failed\n");
|
||||
}
|
||||
free(ticket_original);
|
||||
free(ticket_enc);
|
||||
#endif
|
||||
ticket_v0_t *ticket = (ticket_v0_t*)ticket_template;
|
||||
// maybe we should reject XS00000006 tickets, only allow XS00000003?
|
||||
// iprtf("ticket signature issuer: %s\n", ticket->issuer);
|
||||
// TODO: maybe also validate ticket signature
|
||||
u32 title_id[2];
|
||||
uint32_t title_id[2];
|
||||
GET_UINT32_BE(title_id[0], ticket->title_id, 4);
|
||||
GET_UINT32_BE(title_id[1], ticket->title_id, 0);
|
||||
if (title_id[1] != dsiware_title_id_h) {
|
||||
@ -120,21 +147,18 @@ static int find_ticket_cb(const char* filename, size_t size, void *cb_param) {
|
||||
}
|
||||
|
||||
int setup_ticket_template() {
|
||||
char * ticket_path = alloc_buf();
|
||||
strcpy(ticket_path, nand_root);
|
||||
// strcpy(ticket_path, "sd:/twlnf/dump/");
|
||||
strcat(ticket_path, dsiware_ticket_path);
|
||||
// we'll do AES on that, so must be aligned at least 32 bit
|
||||
// we'll do AES-CCM on that, so must be aligned at least 32 bit
|
||||
ticket_template = memalign(TICKET_ALIGN, TICKET_SIZE);
|
||||
if (ticket_template == 0) {
|
||||
prt("failed to alloc memory\n");
|
||||
free_buf(ticket_path);
|
||||
return -1;
|
||||
}
|
||||
// iprtf("ticket_template addr: %08x\n", (unsigned)ticket_template);
|
||||
char * ticket_dir = alloc_buf();
|
||||
sprintf(ticket_dir, ticket_dir_fmt, nand_root, dsiware_title_id_h);
|
||||
int found = 0;
|
||||
list_dir(ticket_path, 1, find_ticket_cb, &found);
|
||||
free_buf(ticket_path);
|
||||
list_dir(ticket_dir, 1, find_ticket_cb, &found);
|
||||
free_buf(ticket_dir);
|
||||
if (found) {
|
||||
return 0;
|
||||
} else {
|
||||
@ -143,3 +167,156 @@ int setup_ticket_template() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#define TMD_SIZE (sizeof(tmd_header_v0_t) + sizeof(tmd_content_v0_t))
|
||||
|
||||
int tmd_verify(const uint8_t *tmd_buf, const char *tmd_dir, char *tmd_dst_fullname,
|
||||
char *app_src_fullname, uint8_t *app_sha1, int *psize, char *app_dst_fullname,
|
||||
uint8_t *ticket_buf, char *ticket_dst_fullname)
|
||||
{
|
||||
tmd_header_v0_t *header = (tmd_header_v0_t*)tmd_buf;
|
||||
uint32_t title_id[2];
|
||||
GET_UINT32_BE(title_id[0], header->title_id, 4);
|
||||
GET_UINT32_BE(title_id[1], header->title_id, 0);
|
||||
if (title_id[1] != dsiware_title_id_h) {
|
||||
iprtf("not a DSiWare title(%08lx)\n", title_id[1]);
|
||||
return -1;
|
||||
}
|
||||
iprtf("Title ID: %08lx/%c%c%c%c\n", title_id[1],
|
||||
header->title_id[4], header->title_id[5], header->title_id[6], header->title_id[7]);
|
||||
if (header->num_content[0] != 0 || header->num_content[1] != 1) {
|
||||
iprtf("num_content should be 1(%02x%02x)\n",
|
||||
header->num_content[0], header->num_content[1]);
|
||||
return -1;
|
||||
}
|
||||
// I'm ashamed to used app_sha1 for a different thing
|
||||
if (decrypt_cp07_signature(app_sha1, header->sig) != 0) {
|
||||
return -1;
|
||||
}
|
||||
#define SIG_OFFSET (sizeof(header->sig_type) + sizeof(header->sig) + sizeof(header->padding0))
|
||||
#define SIG_LEN (TMD_SIZE - SIG_OFFSET)
|
||||
if (dsi_sha1_verify(app_sha1, tmd_buf + SIG_OFFSET, SIG_LEN) != 0) {
|
||||
#undef SIG_OFFSET
|
||||
#undef SIG_LEN
|
||||
prt("TMD signature verification failed\n");
|
||||
return -1;
|
||||
} else {
|
||||
prt("TMD signature verified\n");
|
||||
}
|
||||
// verify app
|
||||
tmd_content_v0_t *content = (tmd_content_v0_t*)(tmd_buf + sizeof(tmd_header_v0_t));
|
||||
uint32_t content_id;
|
||||
GET_UINT32_BE(content_id, content->content_id, 0);
|
||||
sprintf(app_src_fullname, app_src_fmt, tmd_dir, content_id);
|
||||
prt(app_src_fullname);
|
||||
if ((*psize = sha1_file(app_sha1, app_src_fullname)) == -1) {
|
||||
prt(" <- couldn't open\n");
|
||||
return -1;
|
||||
}
|
||||
if (memcmp(content->sha1, app_sha1, SHA1_LEN) != 0) {
|
||||
prt(" SHA1 doesn't match\n");
|
||||
return -1;
|
||||
} else {
|
||||
prt(" SHA1 verified\n");
|
||||
}
|
||||
// forge ticket
|
||||
memcpy(ticket_buf, ticket_template, TICKET_SIZE);
|
||||
ticket_v0_t *ticket = (ticket_v0_t*)ticket_buf;
|
||||
PUT_UINT32_BE(title_id[0], ticket->title_id, 4);
|
||||
if (dsi_es_block_crypt(ticket_buf, TICKET_SIZE, ENCRYPT) != 0) {
|
||||
prt("weird, failed to forge ticket\n");
|
||||
return -1;
|
||||
}
|
||||
// generate paths
|
||||
sprintf(ticket_dst_fullname, ticket_fullname_fmt, nand_root, title_id[1], title_id[0]);
|
||||
sprintf(tmd_dst_fullname, tmd_fullname_fmt, nand_root, title_id[1], title_id[0]);
|
||||
sprintf(app_dst_fullname, app_fullname_fmt, nand_root, title_id[1], title_id[0], content_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wait_yes_no(const char *);
|
||||
|
||||
void verify(const char *fullname, const uint8_t *digest_verify) {
|
||||
uint8_t digest[SHA1_LEN];
|
||||
int ret = sha1_file(digest, fullname);
|
||||
if (ret == -1) {
|
||||
prt(" but failed to read for verification\n");
|
||||
} else if (memcmp(digest, digest_verify, SHA1_LEN)) {
|
||||
prt(" but verification failed\n");
|
||||
} else {
|
||||
prt(" and verified\n");
|
||||
}
|
||||
}
|
||||
|
||||
void save_and_verify(const char *fullname, uint8_t *buf, size_t len) {
|
||||
prt(fullname);
|
||||
int ret = save_file(fullname, buf, len, 0);
|
||||
if (ret != 0) {
|
||||
prt(" failed to write\n");
|
||||
} else {
|
||||
prt(" written to NAND");
|
||||
uint8_t digest[SHA1_LEN];
|
||||
dsi_sha1_verify(digest, buf, len);
|
||||
verify(fullname, digest);
|
||||
}
|
||||
}
|
||||
|
||||
void install_tmd(const char *tmd_fullname, const char *tmd_dir, int max_size) {
|
||||
// TMD file
|
||||
uint8_t *tmd_buf = malloc(TMD_SIZE);
|
||||
if (tmd_buf == 0) {
|
||||
prt("failed to alloc memory for TMD\n");
|
||||
return;
|
||||
}
|
||||
uint8_t *ticket_buf = memalign(TICKET_ALIGN, TICKET_SIZE);
|
||||
if (ticket_buf == 0) {
|
||||
prt("failed to alloc memory for ticket\n");
|
||||
free(tmd_buf);
|
||||
return;
|
||||
}
|
||||
if (load_block_from_file(tmd_buf, tmd_fullname, 0,
|
||||
sizeof(tmd_header_v0_t) + sizeof(tmd_content_v0_t)) != 0) {
|
||||
prt("failed to load TMD\n");
|
||||
free(tmd_buf);
|
||||
free(ticket_buf);
|
||||
return;
|
||||
}
|
||||
char *tmd_dst_fullname = alloc_buf();
|
||||
char *app_src_fullname = alloc_buf();
|
||||
uint8_t app_sha1[SHA1_LEN];
|
||||
int size;
|
||||
char *app_dst_fullname = alloc_buf();
|
||||
char *ticket_dst_fullname = alloc_buf();
|
||||
if (tmd_verify(tmd_buf, tmd_dir, tmd_dst_fullname,
|
||||
app_src_fullname, app_sha1, &size, app_dst_fullname,
|
||||
ticket_buf, ticket_dst_fullname) == 0) {
|
||||
if (size > max_size) {
|
||||
prt("insufficient NAND space\n");
|
||||
} else if(wait_yes_no("install to NAND?")){
|
||||
// write ticket
|
||||
FILE *f = fopen(ticket_dst_fullname, "r");
|
||||
if (f != 0) {
|
||||
fclose(f);
|
||||
prt("ticket already exist, won't overwrite\n");
|
||||
} else {
|
||||
save_and_verify(ticket_dst_fullname, ticket_buf, TICKET_SIZE);
|
||||
}
|
||||
// write TMD
|
||||
save_and_verify(ticket_dst_fullname, ticket_buf, TICKET_SIZE);
|
||||
// write app
|
||||
prt(app_dst_fullname);
|
||||
if (cp(app_src_fullname, app_dst_fullname) != 0) {
|
||||
prt(" failed to copy\n");
|
||||
} else {
|
||||
prt(" copied to NAND");
|
||||
verify(app_dst_fullname, app_sha1);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(tmd_buf);
|
||||
free(ticket_buf);
|
||||
free_buf(tmd_dst_fullname);
|
||||
free_buf(app_src_fullname);
|
||||
free_buf(app_dst_fullname);
|
||||
free_buf(ticket_dst_fullname);
|
||||
}
|
||||
|
@ -4,3 +4,5 @@ int setup_cp07_pubkey();
|
||||
int decrypt_cp07_signature(unsigned char *out, const unsigned char *in);
|
||||
|
||||
int setup_ticket_template();
|
||||
|
||||
void install_tmd(const char *tmd_fullname, const char *tmd_dir, int max_size);
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include "../term256/term256ext.h"
|
||||
#include "utils.h"
|
||||
|
||||
// globals shared by extern
|
||||
swiSHA1context_t sha1ctx;
|
||||
|
||||
static inline int htoi(char a){
|
||||
@ -20,7 +19,7 @@ static inline int htoi(char a){
|
||||
}
|
||||
}
|
||||
|
||||
int hex2bytes(u8 *out, unsigned byte_len, const char *in){
|
||||
int hex2bytes(uint8_t *out, unsigned byte_len, const char *in){
|
||||
if (strlen(in) < byte_len << 1){
|
||||
iprtf("%s: invalid input length, expecting %u, got %u.\n",
|
||||
__FUNCTION__, (unsigned)byte_len << 1, (unsigned)strlen(in));
|
||||
@ -133,6 +132,9 @@ int load_block_from_file(void *buf, const char *filename, unsigned offset, unsig
|
||||
return ret;
|
||||
}
|
||||
|
||||
// you should have updated the sha1 context before calling save_sha1_file
|
||||
// example: save_file() in this file and backup() in nand.c
|
||||
|
||||
int save_sha1_file(const char *filename) {
|
||||
size_t len_fn = strlen(filename);
|
||||
char *sha1_fn = (char *)malloc(len_fn + 6);
|
||||
|
@ -1,10 +1,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nds.h>
|
||||
#include "common.h"
|
||||
#include <stdint.h>
|
||||
|
||||
int hex2bytes(u8 *out, unsigned byte_len, const char *in);
|
||||
int hex2bytes(uint8_t *out, unsigned byte_len, const char *in);
|
||||
|
||||
const char * to_mebi(size_t size);
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
need term256 to compile
|
||||
git clone https://github.com/Jimmy-Z/term256
|
||||
ln -s term256/term256 twlnf/arm9/term256
|
||||
git clone https://github.com/Jimmy-Z/twlnf
|
||||
git clone https://github.com/Jimmy-Z/term256
|
||||
ln -s term256/term256 twlnf/arm9/term256
|
||||
cd twlnf
|
||||
make
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user