diff --git a/source/tik.c b/source/tik.c index fed43c1..49f6559 100644 --- a/source/tik.c +++ b/source/tik.c @@ -121,7 +121,7 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gam return true; } -void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik) +bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_chain, u64 *out_raw_cert_chain_size) { TikCommonBlock *tik_common_block = NULL; @@ -130,9 +130,34 @@ void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik) u64 signature_size = 0; bool dev_cert = false; + char cert_chain_issuer[0x40] = {0}; + static const char *common_cert_names[] = { "XS00000020", "XS00000022", NULL }; + + u8 *raw_cert_chain = NULL; + u64 raw_cert_chain_size = 0; if (!tik || tik->type == TikType_None || tik->type > TikType_SigHmac160 || tik->size < SIGNED_TIK_MIN_SIZE || tik->size > SIGNED_TIK_MAX_SIZE || \ - !(tik_common_block = tikGetCommonBlock(tik->data)) || tik_common_block->titlekey_type != TikTitleKeyType_Personalized) return; + !(tik_common_block = tikGetCommonBlock(tik->data)) || tik_common_block->titlekey_type != TikTitleKeyType_Personalized || !out_raw_cert_chain || !out_raw_cert_chain_size) + { + LOGFILE("Invalid parameters!"); + return false; + } + + /* Generate raw certificate chain for the new signature issuer (common). */ + dev_cert = (strstr(tik_common_block->issuer, "CA00000004") != NULL); + + for(u8 i = 0; common_cert_names[i] != NULL; i++) + { + sprintf(cert_chain_issuer, "Root-CA%08X-%s", dev_cert ? 4 : 3, common_cert_names[i]); + raw_cert_chain = certGenerateRawCertificateChainBySignatureIssuer(cert_chain_issuer, &raw_cert_chain_size); + if (raw_cert_chain) break; + } + + if (!raw_cert_chain) + { + LOGFILE("Failed to generate raw certificate chain for common ticket signature issuer!"); + return false; + } /* Wipe signature. */ sig_type = signatureGetSigType(tik->data, false); @@ -141,9 +166,8 @@ void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik) memset(signature, 0xFF, signature_size); /* Change signature issuer. */ - dev_cert = (strstr(tik_common_block->issuer, "CA00000004") != NULL); memset(tik_common_block->issuer, 0, sizeof(tik_common_block->issuer)); - sprintf(tik_common_block->issuer, "Root-CA%08X-XS00000020", (dev_cert ? 4 : 3)); + sprintf(tik_common_block->issuer, "%s", cert_chain_issuer); /* Wipe the titlekey block and copy the titlekek-encrypted titlekey to it. */ memset(tik_common_block->titlekey_block, 0, sizeof(tik_common_block->titlekey_block)); @@ -164,6 +188,12 @@ void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik) tik_common_block->sect_hdr_entry_size = 0; memset(tik->data + tik->size, 0, SIGNED_TIK_MAX_SIZE - tik->size); + + /* Update output pointers. */ + *out_raw_cert_chain = raw_cert_chain; + *out_raw_cert_chain_size = raw_cert_chain_size; + + return true; } static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsId *id) diff --git a/source/tik.h b/source/tik.h index 4430f64..5514bb5 100644 --- a/source/tik.h +++ b/source/tik.h @@ -173,9 +173,10 @@ typedef struct { /// Titlekey is also RSA-OAEP unwrapped (if needed) and titlekek decrypted right away. bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gamecard); -/// This will convert a TikTitleKeyType_Personalized ticket into a TikTitleKeyType_Common ticket. +/// Converts a TikTitleKeyType_Personalized ticket into a TikTitleKeyType_Common ticket and generates a raw certificate chain for the new signature issuer. /// Bear in mind the 'size' member from the Ticket parameter will be updated by this function to remove any possible references to ESV1/ESV2 records. -void tikConvertPersonalizedTicketToCommonTicket(Ticket *tik); +/// Raw certificate chain data will be saved to the provided pointers. certGenerateRawCertificateChainBySignatureIssuer() is used internally, so the output buffer must be freed by the user. +bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_chain, u64 *out_raw_cert_chain_size); /// Helper inline functions. diff --git a/source/usb.c b/source/usb.c index d9b823e..e93d3fd 100644 --- a/source/usb.c +++ b/source/usb.c @@ -431,6 +431,7 @@ static void usbDetectionThreadFunc(void *arg) Result rc = 0; int idx = 0; + bool skip_wait = false; Waiter usb_change_event_waiter = waiterForEvent(g_usbStateChangeEvent); Waiter usb_timeout_event_waiter = waiterForUEvent(&g_usbTimeoutEvent); @@ -438,9 +439,12 @@ static void usbDetectionThreadFunc(void *arg) while(true) { - /* Wait until an event is triggered. */ - rc = waitMulti(&idx, -1, usb_change_event_waiter, usb_timeout_event_waiter, exit_event_waiter); - if (R_FAILED(rc)) continue; + if (!skip_wait) + { + /* Wait until an event is triggered. */ + rc = waitMulti(&idx, -1, usb_change_event_waiter, usb_timeout_event_waiter, exit_event_waiter); + if (R_FAILED(rc)) continue; + } rwlockWriteLock(&g_usbDeviceLock); rwlockWriteLock(&(g_usbDeviceInterface.lock)); @@ -451,7 +455,7 @@ static void usbDetectionThreadFunc(void *arg) /* Retrieve current USB connection status. */ /* Only proceed if we're dealing with a status change. */ g_usbHostAvailable = usbIsHostAvailable(); - g_usbSessionStarted = false; + g_usbSessionStarted = skip_wait = false; g_usbTransferRemainingSize = g_usbTransferWrittenSize = 0; g_usbEndpointMaxPacketSize = 0; @@ -463,13 +467,18 @@ static void usbDetectionThreadFunc(void *arg) if (g_usbHostAvailable) { /* Wait until a session is established. */ - /* If the session is successfully established, then we'll get the endpoint max packet size from the data chunk sent by the USB host. */ - /* This is done to accurately know when and where to enable Zero Length Termination (ZLT) packets during bulk transfers. */ - /* As much as I'd like to avoid this, usb:ds doesn't disclose information such as the exact device descriptor and/or speed used by the USB host. */ - g_usbSessionStarted = (usbStartSession() && usbGetMaxPacketSizeFromHost()); - - /* Check if the exit event was triggered while waiting for a session to be established. */ - if (!g_usbSessionStarted && g_usbDetectionThreadExitFlag) break; + g_usbSessionStarted = usbStartSession(); + if (g_usbSessionStarted) + { + /* Get the endpoint max packet size from the response sent by the USB host. */ + /* This is done to accurately know when and where to enable Zero Length Termination (ZLT) packets during bulk transfers. */ + /* As much as I'd like to avoid this, usb:ds doesn't disclose information such as the exact device descriptor and/or speed used by the USB host. */ + /* If this step fails (e.g. unexpected max packet size), the write endpoint will be stalled and we'll wait until a new USB session is established. */ + if (!(skip_wait = !usbGetMaxPacketSizeFromHost())) LOGFILE("USB session successfully established. Endpoint max packet size: 0x%04X.", g_usbEndpointMaxPacketSize); + } else { + /* Check if the exit event was triggered while waiting for a session to be established. */ + if (g_usbDetectionThreadExitFlag) break; + } } rwlockWriteUnlock(&(g_usbDeviceInterface.lock)); @@ -545,14 +554,13 @@ static bool usbGetMaxPacketSizeFromHost(void) usbDsEndpoint_Stall(g_usbDeviceInterface.endpoint_in); rwlockWriteUnlock(&(g_usbDeviceInterface.lock_in)); - /* Reset endpoint max packet size. */ + /* Reset flags. */ + g_usbSessionStarted = false; g_usbEndpointMaxPacketSize = 0; return false; } - LOGFILE("USB session successfully established. Endpoint max packet size: 0x%04X.", g_usbEndpointMaxPacketSize); - return true; } diff --git a/todo.txt b/todo.txt index 4ee62ef..c6bfafe 100644 --- a/todo.txt +++ b/todo.txt @@ -4,7 +4,7 @@ todo: nca: function to write re-encrypted nca headers / nca fs headers (don't forget nca0 please) nca: function to patch the private npdm acid signature from a program nca + patch the acid signature from the nca header - tik: make sure the common crypto cert (XS20, XS22) is available when fakesigning a ticket + tik: option to wipe elicense property mask tik: automatically dump tickets to the SD card? tik: use dumped tickets when the original ones can't be found in the ES savefile?