[wadresign] Initial 'resign' command implementation.

Currently writes the WAD header and certificate chain, including the
extra debug certificate if recrypting as debug.

TODO:
- Write the extra debug certificate when recrypting discs.
- Verify certificate chain ordering for discs. For WADs, it's
  CA, ticket, TMD, dev; for discs, it seems to be ticket, CA, TMD.
- Convert early WAD headers to final. This also requires converting
  the name, if present, to a footer.
This commit is contained in:
David Korth 2019-03-06 23:58:00 -05:00
parent 6088b79dde
commit f09261ae7b
10 changed files with 496 additions and 14 deletions

15
NEWS.md
View File

@ -1,5 +1,20 @@
# Changes
## v2.0 - GUI Release (released 2019/??/??)
New features:
* GUI frontend written using the Qt Toolkit.
* [TODO] Added device querying on Windows.
* wadresign: Command line tool to recrypt and resign WAD files.
* Can read retail, Korean, debug, and early devkit WADs.
* Can create retail (fakesigned), Korean (fakesigned), and debug
(realsigned) WAD files.
* **WARNING:** Use with caution if converting system titles for use
on real hardware.
Low-level changes:
* Rewrote librvth using C++ to improve maintainability.
## v1.1.1 - Brown Paper Bag Release (released 2018/09/17)
* Extracting images broke because it failed when opening a file with 0 bytes

View File

@ -122,3 +122,15 @@ install on retail consoles, since they're encrypted with debug keys.
Do **NOT** attempt to install them by re-encrypting them with the retail
keys. Doing so will most likely result in a brick, especially if the 128 MB
mode IOS WADs are used.
## wadresign
This is a command line tool to resign WAD files to and from any Wii keyset.
Conversion to Debug will be realsigned. Conversion to retail or Korean will
be fakesigned.
In addition, this tool supports reading early devkit WADs, which makes it
possible to convert them to run on emulators and/or later devkits.
**WARNING:** Use with caution if converting a system title for installation
on read hardware, since this may result in an unrecoverable brick.

View File

@ -610,6 +610,9 @@ int RvtH::recryptWiiPartitions(unsigned int bank,
// Certificate chain order for retail is Ticket, CA, TMD.
// TODO: Verify for debug! (and write the dev cert?)
// NOTE: WAD cert chain order is CA, Ticket, TMD...
// (CA, Ticket, TMD, Dev for debug)
// TODO: Verify all of this.
p_cert_chain = &hdr_new.u8[data_pos];
memcpy(p_cert_chain, cert_ticket, sizeof(*cert_ticket));
p_cert_chain += sizeof(*cert_ticket);

View File

@ -10,11 +10,13 @@ SET(wadresign_SRCS
main.c
print-info.c
wad-fns.c
resign-wad.c
)
# Headers.
SET(wadresign_H
print-info.h
wad-fns.h
resign-wad.h
)
IF(WIN32)
SET(wadresign_RC resource.rc)

View File

@ -36,6 +36,7 @@
#endif /* _WIN32 */
#include "print-info.h"
#include "resign-wad.h"
#ifdef _MSC_VER
# define RVTH_CDECL __cdecl
@ -89,6 +90,11 @@ static void print_help(const TCHAR *argv0)
"info file.wad\n"
"- Print information about the specified WAD file.\n"
"\n"
"resign source.wad dest.wad\n"
" - Resigns source.wad and creates dest.wad using the new key.\n"
" Default converts Retail/Korean WADs to Debug, and\n"
" Debug WADs to Retail.\n"
"\n"
"Options:\n"
"\n"
" -k, --recrypt=KEY Recrypt the WAD using the specified KEY:\n"
@ -196,9 +202,20 @@ int RVTH_CDECL _tmain(int argc, TCHAR *argv[])
return EXIT_FAILURE;
}
ret = print_wad_info(argv[optind+1]);
} else if (!_tcscmp(argv[optind], _T("resign"))) {
// Resign a WAD.
if (argc < optind+2) {
print_error(argv[0], _T("WAD filenames not specified"));
return EXIT_FAILURE;
} else if (argc < optind+3) {
print_error(argv[0], _T("Output WAD filename not specified"));
return EXIT_FAILURE;
}
ret = resign_wad(argv[optind+1], argv[optind+2], recrypt_key);
} else {
// If the "command" contains a slash or dot (or backslash on Windows),
// assume it's a filename and handle it as 'info'.
// TODO: If two filenames are specified, handle it as 'resign'.
const TCHAR *p;
bool isFilename = false;
for (p = argv[optind]; *p != 0; p++) {

View File

@ -120,11 +120,12 @@ const char *identify_wad_type(const uint8_t *buf, size_t buf_len, bool *pIsEarly
}
/**
* 'info' command.
* @param wad_filename WAD filename.
* 'info' command. (internal function)
* @param f_wad WAD file.
* @param wad_filename WAD filename. (for error messages)
* @return 0 on success; negative POSIX error code or positive ID code on error.
*/
int print_wad_info(const TCHAR *wad_filename)
int print_wad_info_FILE(FILE *f_wad, const TCHAR *wad_filename)
{
int ret;
size_t size;
@ -143,17 +144,8 @@ int print_wad_info(const TCHAR *wad_filename)
const char *issuer_ticket, *issuer_tmd;
RVL_SigStatus_e sig_status_ticket, sig_status_tmd;
// Open the WAD file.
FILE *f_wad = _tfopen(wad_filename, _T("rb"));
if (!f_wad) {
int err = errno;
fputs("*** ERROR opening WAD file '", stderr);
_fputts(wad_filename, stderr);
fprintf(stderr, "': %s\n", strerror(err));
return -err;
}
// Read the WAD header.
rewind(f_wad);
size = fread(&header, 1, sizeof(header), f_wad);
if (size != sizeof(header)) {
int err = errno;
@ -292,6 +284,30 @@ int print_wad_info(const TCHAR *wad_filename)
end:
free(tmd);
return ret;
}
/**
* 'info' command.
* @param wad_filename WAD filename.
* @return 0 on success; negative POSIX error code or positive ID code on error.
*/
int print_wad_info(const TCHAR *wad_filename)
{
int ret;
// Open the WAD file.
FILE *f_wad = _tfopen(wad_filename, _T("rb"));
if (!f_wad) {
int err = errno;
fputs("*** ERROR opening WAD file '", stderr);
_fputts(wad_filename, stderr);
fprintf(stderr, "': %s\n", strerror(err));
return -err;
}
// Print the WAD info.
ret = print_wad_info_FILE(f_wad, wad_filename);
fclose(f_wad);
return ret;
}

View File

@ -22,7 +22,7 @@
#define __RVTHTOOL_WADRESIGN_PRINT_INFO_H__
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include "librvth/tcharx.h"
@ -54,6 +54,14 @@ const char *issuer_type(RVL_Cert_Issuer issuer);
*/
const char *identify_wad_type(const uint8_t *buf, size_t buf_len, bool *pIsEarly);
/**
* 'info' command. (internal function)
* @param f_wad Opened WAD file.
* @param wad_filename WAD filename. (for error messages)
* @return 0 on success; negative POSIX error code or positive ID code on error.
*/
int print_wad_info_FILE(FILE *f_wad, const TCHAR *wad_filename);
/**
* 'info' command.
* @param wad_filename WAD filename.

363
src/wadresign/resign-wad.c Normal file
View File

@ -0,0 +1,363 @@
/***************************************************************************
* RVT-H Tool: WAD Resigner *
* print-info.c: Print WAD information. *
* *
* Copyright (c) 2018-2019 by David Korth. *
* *
* 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 2 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 "resign-wad.h"
#include "print-info.h"
#include "wad-fns.h"
// libwiicrypto
#include "libwiicrypto/byteswap.h"
#include "libwiicrypto/cert.h"
#include "libwiicrypto/wii_wad.h"
#include "libwiicrypto/sig_tools.h"
// C includes.
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ISALNUM(c) isalnum((unsigned char)c)
typedef union _WAD_Header {
Wii_WAD_Header wad;
Wii_WAD_Header_EARLY wadE;
} WAD_Header;
// Read buffer.
typedef union _rdbuf_t {
uint8_t u8[1024*1024];
RVL_Ticket ticket;
RVL_TMD_Header tmdHeader;
} rdbuf_t;
/**
* Align the file pointer to the next 64-byte boundary.
* @param fp File pointer.
*/
static inline void fpAlign(FILE *fp)
{
int64_t offset = ftello(fp);
if ((offset & 63) != 0) {
offset = ALIGN(64, offset);
fseeko(fp, offset, SEEK_SET);
}
}
/**
* 'resign' command.
* @param src_wad [in] Source WAD.
* @param dest_wad [in] Destination WAD.
* @param recrypt_key [in] Key for recryption. (-1 for default)
* @return 0 on success; negative POSIX error code or positive ID code on error.
*/
int resign_wad(const TCHAR *src_wad, const TCHAR *dest_wad, int recrypt_key)
{
int ret;
size_t size;
bool isEarly = false;
WAD_Header header;
WAD_Info_t wadInfo;
RVL_CryptoType_e src_key;
// Files.
FILE *f_src_wad = NULL, *f_dest_wad = NULL;
// Certificates.
const RVL_Cert_RSA4096_RSA2048 *cert_CA;
const RVL_Cert_RSA2048 *cert_ticket, *cert_TMD;
const RVL_Cert_RSA2048_ECC *cert_dev;
// Read buffer.
rdbuf_t *buf = NULL;
// Open the source WAD file.
f_src_wad = _tfopen(src_wad, _T("rb"));
if (!f_src_wad) {
int err = errno;
fputs("*** ERROR opening source WAD file '", stderr);
_fputts(src_wad, stderr);
fprintf(stderr, "': %s\n", strerror(err));
return -err;
}
// Print the WAD information.
ret = print_wad_info_FILE(f_src_wad, src_wad);
if (ret != 0) {
// Error printing the WAD information.
return ret;
}
// Re-read the WAD header and parse the addresses.
rewind(f_src_wad);
size = fread(&header, 1, sizeof(header), f_src_wad);
if (size != sizeof(header)) {
int err = errno;
fputs("*** ERROR reading WAD file '", stderr);
_fputts(src_wad, stderr);
fprintf(stderr, "': %s\n", strerror(err));
ret = -err;
goto end;
}
// Identify the WAD type.
// TODO: More extensive error handling?
// NOTE: Not saving the string, since we only need to knwo if
// it's an early WAD or not.
if (identify_wad_type((const uint8_t*)&header, sizeof(header), &isEarly) == NULL) {
// Unrecognized WAD type.
fputs("*** ERROR: WAD file '", stderr);
_fputts(src_wad, stderr);
fprintf(stderr, "' is not valid.\n");
ret = 1;
goto end;
}
// Determine the sizes and addresses of various components.
if (!isEarly) {
ret = getWadInfo(&header.wad, &wadInfo);
} else {
ret = getWadInfo_early(&header.wadE, &wadInfo);
}
if (ret != 0) {
// Unable to get WAD information.
fputs("*** ERROR: WAD file '", stderr);
_fputts(src_wad, stderr);
fprintf(stderr, "' is not valid.");
ret = 2;
goto end;
}
// Verify the ticket and TMD sizes.
if (wadInfo.ticket_size != sizeof(RVL_Ticket)) {
// Incorrect ticket size.
fputs("*** ERROR: WAD file '", stderr);
_fputts(src_wad, stderr);
fprintf(stderr, "' ticket size is incorrect. (%u; should be %u)\n",
wadInfo.ticket_size, (uint32_t)sizeof(RVL_Ticket));
ret = 3;
goto end;
} else if (wadInfo.tmd_size < sizeof(RVL_TMD_Header)) {
fputs("*** ERROR: WAD file '", stderr);
_fputts(src_wad, stderr);
fprintf(stderr, "' TMD size is too small. (%u; should be at least %u)\n",
wadInfo.tmd_size, (uint32_t)sizeof(RVL_TMD_Header));
ret = 4;
goto end;
} else if (wadInfo.tmd_size > 1*1024*1024) {
// Too big.
fputs("*** ERROR: WAD file '", stderr);
_fputts(src_wad, stderr);
fprintf(stderr, "' TMD size is too big. (%u; should be less than 1 MB)\n",
wadInfo.tmd_size);
ret = 5;
goto end;
}
// Allocate the memory buffer.
buf = malloc(sizeof(*buf));
if (!buf) {
fputs("*** ERROR: Unable to allocate memory buffer.\n", stderr);
ret = 6;
goto end;
}
// Load the ticket.
fseek(f_src_wad, wadInfo.ticket_address, SEEK_SET);
size = fread(&buf->ticket, 1, sizeof(buf->ticket), f_src_wad);
if (size != sizeof(RVL_Ticket)) {
// Read error.
fputs("*** ERROR: WAD file '", stderr);
_fputts(src_wad, stderr);
fputs("': Unable to read the ticket.\n", stderr);
ret = 7;
goto end;
}
// Check the encryption key.
switch (cert_get_issuer_from_name(buf->ticket.issuer)) {
case RVL_CERT_ISSUER_RETAIL_TICKET:
// Retail may be either Common Key or Korean Key.
switch (buf->ticket.common_key_index) {
case 0:
src_key = RVL_CryptoType_Retail;
break;
case 1:
src_key = RVL_CryptoType_Korean;
break;
default: {
const char *s_key;
// NOTE: A good number of retail WADs have an
// incorrect common key index for some reason.
fputs("*** WARNING: WAD file '", stderr);
_fputts(src_wad, stderr);
fprintf(stderr, "': Invalid common key index %u.\n",
buf->ticket.common_key_index);
if (buf->ticket.title_id.u8[7] == 'K') {
s_key = "Korean";
src_key = RVL_CryptoType_Korean;
} else {
s_key = "retail";
src_key = RVL_CryptoType_Retail;
}
fprintf(stderr, "*** Assuming %s common key based on game ID.\n\n", s_key);
break;
}
}
break;
case RVL_CERT_ISSUER_DEBUG_TICKET:
src_key = RVL_CryptoType_Debug;
break;
default:
fputs("*** ERROR: WAD file '", stderr);
_fputts(src_wad, stderr);
fputs("': Unknown issuer.\n", stderr);
ret = 8;
goto end;
}
if (recrypt_key == -1) {
// Select the "opposite" key.
switch (src_key) {
case RVL_CryptoType_Retail:
case RVL_CryptoType_Korean:
recrypt_key = RVL_CryptoType_Debug;
break;
case RVL_CryptoType_Debug:
recrypt_key = RVL_CryptoType_Retail;
break;
default:
// Should not happen...
assert(!"This shouldn't happen!");
fputs("*** ERROR: Unable to select encryption key.\n", stderr);
ret = 9;
goto end;
}
} else {
if ((RVL_CryptoType_e)recrypt_key == src_key) {
// No point in recrypting to the same key...
fputs("*** ERROR: Cannot recrypt to the same key.\n", stderr);
ret = 10;
goto end;
}
}
// Open the destination WAD file.
f_dest_wad = _tfopen(dest_wad, _T("wb"));
if (!f_dest_wad) {
int err = errno;
fputs("*** ERROR opening destination WAD file '", stderr);
_fputts(dest_wad, stderr);
fprintf(stderr, "' for write: %s\n", strerror(err));
ret = -err;
goto end;
}
// TODO: Convert early WAD header to final WAD.
printf("Writing certificate chain...\n");
// Get the certificates.
if (recrypt_key != RVL_CryptoType_Debug) {
// Retail certificates.
// Order: CA, Ticket, TMD
cert_CA = (const RVL_Cert_RSA4096_RSA2048*)cert_get(RVL_CERT_ISSUER_DEBUG_CA);
cert_ticket = (const RVL_Cert_RSA2048*)cert_get(RVL_CERT_ISSUER_DEBUG_TICKET);
cert_TMD = (const RVL_Cert_RSA2048*)cert_get(RVL_CERT_ISSUER_DEBUG_TMD);
cert_dev = NULL;
header.wad.cert_chain_size = cpu_to_be32((uint32_t)(
sizeof(*cert_CA) + sizeof(*cert_ticket) +
sizeof(*cert_TMD)));
} else {
// Debug certificates.
// Order: CA, Ticket, TMD
cert_CA = (const RVL_Cert_RSA4096_RSA2048*)cert_get(RVL_CERT_ISSUER_DEBUG_CA);
cert_ticket = (const RVL_Cert_RSA2048*)cert_get(RVL_CERT_ISSUER_DEBUG_TICKET);
cert_TMD = (const RVL_Cert_RSA2048*)cert_get(RVL_CERT_ISSUER_DEBUG_TMD);
cert_dev = (const RVL_Cert_RSA2048_ECC*)cert_get(RVL_CERT_ISSUER_DEBUG_DEV);
header.wad.cert_chain_size = cpu_to_be32((uint32_t)(
sizeof(*cert_CA) + sizeof(*cert_ticket) +
sizeof(*cert_TMD) + sizeof(*cert_dev)));
}
// Write the WAD header.
size = fwrite(&header.wad, 1, sizeof(header.wad), f_dest_wad);
if (size != sizeof(header.wad)) {
int err = errno;
fprintf(stderr, "*** ERROR writing WAD header: %s\n", strerror(err));
ret = -err;
goto end;
}
// 64-byte alignment.
fpAlign(f_dest_wad);
// Write the certificates.
size = fwrite(cert_CA, 1, sizeof(*cert_CA), f_dest_wad);
if (size != sizeof(*cert_CA)) {
int err = errno;
fprintf(stderr, "*** ERROR writing WAD certificate chain: %s\n", strerror(err));
ret = -err;
goto end;
}
size = fwrite(cert_ticket, 1, sizeof(*cert_ticket), f_dest_wad);
if (size != sizeof(*cert_ticket)) {
int err = errno;
fprintf(stderr, "*** ERROR writing WAD certificate chain: %s\n", strerror(err));
ret = -err;
goto end;
}
size = fwrite(cert_TMD, 1, sizeof(*cert_TMD), f_dest_wad);
if (size != sizeof(*cert_TMD)) {
int err = errno;
fprintf(stderr, "*** ERROR writing WAD certificate chain: %s\n", strerror(err));
ret = -err;
goto end;
}
if (cert_dev) {
size = fwrite(cert_dev, 1, sizeof(*cert_dev), f_dest_wad);
if (size != sizeof(*cert_dev)) {
int err = errno;
fprintf(stderr, "*** ERROR writing WAD certificate chain: %s\n", strerror(err));
ret = -err;
goto end;
}
}
// 64-byte alignment.
fpAlign(f_dest_wad);
// TODO: Read and handle ticket, TMD, data, and footer/name.
printf("Recrypting the ticket and TMD...\n");
ret = 0;
end:
free(buf);
if (f_dest_wad) {
// TODO: Delete if an error occurred?
fclose(f_dest_wad);
}
if (f_src_wad) {
fclose(f_src_wad);
}
return ret;
}

View File

@ -0,0 +1,44 @@
/***************************************************************************
* RVT-H Tool: WAD Resigner *
* resign-wad.h: Re-sign a WAD file. *
* *
* Copyright (c) 2018-2019 by David Korth. *
* *
* 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 2 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/>. *
***************************************************************************/
#ifndef __RVTHTOOL_WADRESIGN_RESIGN_WAD_H__
#define __RVTHTOOL_WADRESIGN_RESIGN_WAD_H__
#include "librvth/tcharx.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* 'resign' command.
* @param src_wad [in] Source WAD.
* @param dest_wad [in] Destination WAD.
* @param recrypt_key [in] Key for recryption. (-1 for default)
* @return 0 on success; negative POSIX error code or positive ID code on error.
*/
int resign_wad(const TCHAR *src_wad, const TCHAR *dest_wad, int recrypt_key);
#ifdef __cplusplus
}
#endif
#endif /* __RVTHTOOL_WADRESIGN_PRINT_INFO_H__ */

View File

@ -19,7 +19,9 @@
***************************************************************************/
#include "wad-fns.h"
#include "libwiicrypto/byteswap.h"
#include "libwiicrypto/common.h"
/**
* Get WAD info for a standard WAD file.