mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-12-22 16:42:11 +00:00
Rework signature/cert/tik interfaces.
* signature: add comments to SignatureType enum entries about the exact signing algorithms and padding schemes used. * signature: rename signatureGetSigType() -> signatureGetTypeFromSignedBlob(). * signature: rename signatureIsValidSigType() -> signatureIsValidType(). * signature: rename signatureGetSigSize() -> signatureGetSigSizeByType(). * signature: rename signatureGetBlockSize() -> signatureGetBlockSizeByType(). * signature: rename signatureGetSig() -> signatureGetSigFromSignedBlob(). * signature: rename signatureGetPayload() -> signatureGetPayloadFromSignedBlob(). * signature: add signatureGetBlockSizeFromSignedBlob(). * cert: add more comments to the code. * cert: update code to match signature interface changes. * cert: add CERT_RSA_PUB_EXP_SIZE macro. * cert: change public_exponent field in CertPublicKeyBlockRsa* structs from u32 to u8 array. * cert: add size field to CertificateChain struct. * cert: rename certGetCommonBlock() -> certGetCommonBlockFromSignedCertBlob. * cert: rename certGetPublicKeySize() -> certGetPublicKeySizeByType(). * cert: rename certGetPublicKeyBlockSize() -> certGetPublicKeyBlockSizeByType(). * cert: rename certIsValidCertificate() -> certIsValidSignedCertBlob(). * cert: rename certGetSignedCertificateSize() -> certGetSignedCertBlobSize(). * cert: rename certGetSignedCertificateHashAreaSize() -> certGetSignedCertBlobHashAreaSize(). * cert: remove certGetPublicKey(), certGetPublicExponent() and certCalculateRawCertificateChainSize(). * cert: add certGetPublicKeyTypeFromCommonBlock(), certGetPublicKeyTypeFromSignedCertBlob(), certGetPublicKeySizeFromSignedCertBlob(), certGetPublicKeyBlockSizeFromSignedCertBlob(), certGetPublicKeyFromSignedCertBlob(), certGetPublicExponentFromSignedCertBlob(), certIsValidCertificate() (w/diff func sig), certGetCommonBlockFromCertificate(), certGetPublicKeyTypeFromCertificate(), certGetPublicKeySizeFromCertificate(), certGetPublicKeyBlockSizeFromCertificate(), certGetPublicKeyFromCertificate(), certGetPublicExponentFromCertificate() and certGetHashAreaSizeFromCertificate() functions. * cert: avoid byteswapping the public key type value in multiple places -- it is now only being done in certGetPublicKeyTypeFromCommonBlock(). * cert: call certFreeCertificateChain() in _certRetrieveCertificateChainBySignatureIssuer() before attempting to retrieve the certificate chain. * cert: other minor changes and corrections. * tik: update code to match signature interface changes. * tik: add missing comments to TikPropertyMask enum entries. * tik: add key_generation, enc_titlekey_str and dec_titlekey_str fields to Ticket struct. * tik: update tikRetrieveTicketByRightsId() to also take in a key_generation argument, instead of getting it from the rights ID (which could fail if it's using a key generation lower than HOS 3.0.1) or the key_generation field from the common ticket block (which could fail if the ticket has been tampered by certain tools). * tik: rename tikGetCommonBlock() -> tikGetCommonBlockFromSignedTicketBlob(). * tik: change function signature for tikGetTicketSectionRecordsBlockSize(). * tik: rename tikIsValidTicket() -> tikIsValidSignedTicketBlob(). * tik: rename tikGetSignedTicketSize() -> tikGetSignedTicketBlobSize(). * tik: rename tikGetSignedTicketHashAreaSize() -> tikGetSignedTicketBlobHashAreaSize(). * tik: rename tikGetEncryptedTitleKeyFromTicket() -> tikGetEncryptedTitleKey(). * tik: add tikIsValidTicket() (w/diff func sig), tikGetCommonBlockFromTicket(), tikGetHashAreaSizeFromTicket(), tikFixTamperedCommonTicket(), tikVerifyRsa2048Sha256Signature() and tikDecryptVolatileTicket() functions. Ticket signature verification is only carried out for common tickets in tikFixTamperedCommonTicket(). * tik: change argument order in tikGetTicketEntryOffsetFromTicketList() and tikRetrieveTicketEntryFromTicketBin(). * tik: add TIK_COMMON_CERT_NAME and TIK_DEV_CERT_ISSUER macros. * tik: use a scoped lock when calling tikRetrieveTicketFromEsSaveDataByRightsId(). * tik: simplify certificate chain retrieval steps in tikConvertPersonalizedTicketToCommonTicket() by always using the XS00000020 certificate. * tik: wipe license_type and property_mask fields in tikConvertPersonalizedTicketToCommonTicket(). * tik: other minor changes and corrections. Other changes include: * keys: fix key generation checks in keysGetNcaKeyAreaKeyEncryptionKey() and keysGetTicketCommonKey(). * rsa: move core logic from rsa2048VerifySha256BasedPssSignature() into a new function: rsa2048VerifySha256BasedSignature(). * rsa: add rsa2048VerifySha256BasedPkcs1v15Signature() function.
This commit is contained in:
parent
ecaeddf356
commit
c1b76fb2d9
12 changed files with 663 additions and 387 deletions
|
@ -2869,8 +2869,6 @@ static bool saveTicket(void *userdata)
|
|||
NcaContext *nca_ctx = NULL;
|
||||
|
||||
Ticket tik = {0};
|
||||
TikCommonBlock *tik_common_block = NULL;
|
||||
char enc_titlekey_str[33] = {0};
|
||||
|
||||
u32 crc = 0;
|
||||
char *filename = NULL;
|
||||
|
@ -2916,21 +2914,14 @@ static bool saveTicket(void *userdata)
|
|||
goto end;
|
||||
}
|
||||
|
||||
if (!tik.size)
|
||||
if (!nca_ctx->titlekey_retrieved)
|
||||
{
|
||||
consolePrint("failed to retrieve ticket (unavailable?)\ntry launching nxdumptool while overriding the title you wish to dump a ticket from\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Retrieve ticket common block. */
|
||||
if (!(tik_common_block = tikGetCommonBlock(tik.data)))
|
||||
{
|
||||
consolePrint("failed to get tik common block\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Remove console-specific data, if needed. */
|
||||
if (remove_console_data && tik_common_block->titlekey_type == TikTitleKeyType_Personalized && !tikConvertPersonalizedTicketToCommonTicket(&tik, NULL, NULL))
|
||||
if (remove_console_data && tikIsPersonalizedTicket(&tik) && !tikConvertPersonalizedTicketToCommonTicket(&tik, NULL, NULL))
|
||||
{
|
||||
consolePrint("failed to convert personalized ticket to common ticket\n");
|
||||
goto end;
|
||||
|
@ -2945,10 +2936,9 @@ static bool saveTicket(void *userdata)
|
|||
|
||||
if (!saveFileData(filename, tik.data, tik.size)) goto end;
|
||||
|
||||
utilsGenerateHexStringFromData(enc_titlekey_str, MAX_ELEMENTS(enc_titlekey_str), tik.enc_titlekey, sizeof(tik.enc_titlekey), false);
|
||||
|
||||
consolePrint("rights id: %s\n", tik.rights_id_str);
|
||||
consolePrint("titlekey: %s\n\n", enc_titlekey_str);
|
||||
consolePrint("encrypted titlekey: %s\n", tik.enc_titlekey_str);
|
||||
consolePrint("decrypted titlekey: %s\n\n", tik.dec_titlekey_str);
|
||||
|
||||
consolePrint("successfully saved ticket as \"%s\"\n", filename);
|
||||
success = true;
|
||||
|
@ -5003,7 +4993,7 @@ static void nspThreadFunc(void *arg)
|
|||
bool retrieve_tik_cert = (!remove_titlekey_crypto && tik.size > 0);
|
||||
if (retrieve_tik_cert)
|
||||
{
|
||||
if (!(tik_common_block = tikGetCommonBlock(tik.data)))
|
||||
if (!(tik_common_block = tikGetCommonBlockFromTicket(&tik)))
|
||||
{
|
||||
consolePrint("tik common block failed");
|
||||
goto end;
|
||||
|
|
|
@ -30,17 +30,17 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SIGNED_CERT_MAX_SIZE sizeof(CertSigRsa4096PubKeyRsa4096)
|
||||
#define SIGNED_CERT_MIN_SIZE sizeof(CertSigHmac160PubKeyEcc480)
|
||||
#define SIGNED_CERT_MAX_SIZE sizeof(CertSigRsa4096PubKeyRsa4096)
|
||||
|
||||
#define CERT_RSA_PUB_EXP_SIZE 4
|
||||
|
||||
#define GENERATE_CERT_STRUCT(sigtype, pubkeytype, certsize) \
|
||||
\
|
||||
typedef struct { \
|
||||
SignatureBlock##sigtype sig_block; \
|
||||
CertCommonBlock cert_common_block; \
|
||||
CertPublicKeyBlock##pubkeytype pub_key_block; \
|
||||
} CertSig##sigtype##PubKey##pubkeytype; \
|
||||
\
|
||||
NXDT_ASSERT(CertSig##sigtype##PubKey##pubkeytype, certsize);
|
||||
|
||||
typedef enum {
|
||||
|
@ -63,7 +63,7 @@ NXDT_ASSERT(CertCommonBlock, 0x88);
|
|||
/// RSA-4096 public key block. Placed after the certificate common block.
|
||||
typedef struct {
|
||||
u8 public_key[0x200];
|
||||
u32 public_exponent;
|
||||
u8 public_exponent[CERT_RSA_PUB_EXP_SIZE];
|
||||
u8 padding[0x34];
|
||||
} CertPublicKeyBlockRsa4096;
|
||||
|
||||
|
@ -72,7 +72,7 @@ NXDT_ASSERT(CertPublicKeyBlockRsa4096, 0x238);
|
|||
/// RSA-2048 public key block. Placed after the certificate common block.
|
||||
typedef struct {
|
||||
u8 public_key[0x100];
|
||||
u32 public_exponent;
|
||||
u8 public_exponent[CERT_RSA_PUB_EXP_SIZE];
|
||||
u8 padding[0x34];
|
||||
} CertPublicKeyBlockRsa2048;
|
||||
|
||||
|
@ -133,10 +133,11 @@ typedef struct {
|
|||
u8 data[SIGNED_CERT_MAX_SIZE]; ///< Raw certificate data.
|
||||
} Certificate;
|
||||
|
||||
/// Used to store two or more certificates.
|
||||
/// Used to store multiple certificates.
|
||||
typedef struct {
|
||||
u32 count;
|
||||
Certificate *certs;
|
||||
u32 count; ///< Number of certificates in this chain.
|
||||
u64 size; ///< Raw certificate chain size (when concatenated).
|
||||
Certificate *certs; ///< Certificate array.
|
||||
} CertificateChain;
|
||||
|
||||
/// Retrieves a certificate by its name (e.g. "CA00000003", "XS00000020", etc.).
|
||||
|
@ -145,16 +146,136 @@ bool certRetrieveCertificateByName(Certificate *dst, const char *name);
|
|||
/// Retrieves a certificate chain by a full signature issuer string (e.g. "Root-CA00000003-XS00000020").
|
||||
bool certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer);
|
||||
|
||||
/// Returns a pointer to a dynamically allocated buffer that contains the raw contents from the certificate chain matching the input signature issuer. It must be freed by the user.
|
||||
/// NULL is returned if an error occurs.
|
||||
/// Returns a pointer to a dynamically allocated buffer that holds the concatenated raw contents from the certificate chain matching the input signature issuer.
|
||||
/// The returned buffer must be freed by the user.
|
||||
/// Returns NULL if an error occurs.
|
||||
u8 *certGenerateRawCertificateChainBySignatureIssuer(const char *issuer, u64 *out_size);
|
||||
|
||||
/// Returns a pointer to a dynamically allocated buffer that contains the raw contents from the certificate chain matching the input Rights ID (stored in the inserted gamecard).
|
||||
/// It must be freed by the user.
|
||||
/// NULL is returned if an error occurs.
|
||||
/// Returns a pointer to a dynamically allocated buffer that holds the concatenated raw contents from the certificate chain matching the input rights ID in the inserted gamecard.
|
||||
/// The returned buffer must be freed by the user.
|
||||
/// Returns NULL if an error occurs.
|
||||
u8 *certRetrieveRawCertificateChainFromGameCardByRightsId(const FsRightsId *id, u64 *out_size);
|
||||
|
||||
/// Helper inline functions.
|
||||
/// General purpose helper inline functions.
|
||||
|
||||
NX_INLINE bool certIsValidPublicKeyType(u32 type)
|
||||
{
|
||||
return (type < CertPubKeyType_Count);
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetPublicKeySizeByType(u32 type)
|
||||
{
|
||||
return (u64)(type == CertPubKeyType_Rsa4096 ? MEMBER_SIZE(CertPublicKeyBlockRsa4096, public_key) : \
|
||||
(type == CertPubKeyType_Rsa2048 ? MEMBER_SIZE(CertPublicKeyBlockRsa2048, public_key) : \
|
||||
(type == CertPubKeyType_Ecc480 ? MEMBER_SIZE(CertPublicKeyBlockEcc480, public_key) : 0)));
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetPublicKeyBlockSizeByType(u32 type)
|
||||
{
|
||||
return (u64)(type == CertPubKeyType_Rsa4096 ? sizeof(CertPublicKeyBlockRsa4096) : \
|
||||
(type == CertPubKeyType_Rsa2048 ? sizeof(CertPublicKeyBlockRsa2048) : \
|
||||
(type == CertPubKeyType_Ecc480 ? sizeof(CertPublicKeyBlockEcc480) : 0)));
|
||||
}
|
||||
|
||||
NX_INLINE u32 certGetPublicKeyTypeFromCommonBlock(CertCommonBlock *cert_common_block)
|
||||
{
|
||||
return (cert_common_block ? __builtin_bswap32(cert_common_block->pub_key_type) : CertPubKeyType_Count);
|
||||
}
|
||||
|
||||
/// Helper inline functions for signed certificate blobs.
|
||||
|
||||
NX_INLINE CertCommonBlock *certGetCommonBlockFromSignedCertBlob(void *buf)
|
||||
{
|
||||
return (CertCommonBlock*)signatureGetPayloadFromSignedBlob(buf, true);
|
||||
}
|
||||
|
||||
NX_INLINE bool certIsValidSignedCertBlob(void *buf)
|
||||
{
|
||||
CertCommonBlock *cert_common_block = certGetCommonBlockFromSignedCertBlob(buf);
|
||||
return (cert_common_block && certIsValidPublicKeyType(certGetPublicKeyTypeFromCommonBlock(cert_common_block)));
|
||||
}
|
||||
|
||||
NX_INLINE u32 certGetPublicKeyTypeFromSignedCertBlob(void *buf)
|
||||
{
|
||||
return (certIsValidSignedCertBlob(buf) ? certGetPublicKeyTypeFromCommonBlock(certGetCommonBlockFromSignedCertBlob(buf)) : CertPubKeyType_Count);
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetPublicKeySizeFromSignedCertBlob(void *buf)
|
||||
{
|
||||
return certGetPublicKeySizeByType(certGetPublicKeyTypeFromSignedCertBlob(buf));
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetPublicKeyBlockSizeFromSignedCertBlob(void *buf)
|
||||
{
|
||||
return certGetPublicKeyBlockSizeByType(certGetPublicKeyTypeFromSignedCertBlob(buf));
|
||||
}
|
||||
|
||||
NX_INLINE u8 *certGetPublicKeyFromSignedCertBlob(void *buf)
|
||||
{
|
||||
return (certIsValidSignedCertBlob(buf) ? ((u8*)certGetCommonBlockFromSignedCertBlob(buf) + sizeof(CertCommonBlock)) : NULL);
|
||||
}
|
||||
|
||||
NX_INLINE u8 *certGetPublicExponentFromSignedCertBlob(void *buf)
|
||||
{
|
||||
u32 pub_key_type = certGetPublicKeyTypeFromSignedCertBlob(buf);
|
||||
u8 *public_key = certGetPublicKeyFromSignedCertBlob(buf);
|
||||
return (pub_key_type < CertPubKeyType_Ecc480 ? (public_key + certGetPublicKeySizeByType(pub_key_type)) : NULL); // Only allow RSA public key types.
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetSignedCertBlobSize(void *buf)
|
||||
{
|
||||
return (certIsValidSignedCertBlob(buf) ? (signatureGetBlockSizeFromSignedBlob(buf, true) + sizeof(CertCommonBlock) + certGetPublicKeyBlockSizeFromSignedCertBlob(buf)) : 0);
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetSignedCertBlobHashAreaSize(void *buf)
|
||||
{
|
||||
return (certIsValidSignedCertBlob(buf) ? (sizeof(CertCommonBlock) + certGetPublicKeyBlockSizeFromSignedCertBlob(buf)) : 0);
|
||||
}
|
||||
|
||||
/// Helper inline functions for Certificate elements.
|
||||
|
||||
NX_INLINE bool certIsValidCertificate(Certificate *cert)
|
||||
{
|
||||
return (cert && cert->type > CertType_None && cert->type < CertType_Count && cert->size >= SIGNED_CERT_MIN_SIZE && cert->size <= SIGNED_CERT_MAX_SIZE && \
|
||||
certIsValidSignedCertBlob(cert->data));
|
||||
}
|
||||
|
||||
NX_INLINE CertCommonBlock *certGetCommonBlockFromCertificate(Certificate *cert)
|
||||
{
|
||||
return (certIsValidCertificate(cert) ? certGetCommonBlockFromSignedCertBlob(cert->data) : NULL);
|
||||
}
|
||||
|
||||
NX_INLINE u32 certGetPublicKeyTypeFromCertificate(Certificate *cert)
|
||||
{
|
||||
return (certIsValidCertificate(cert) ? certGetPublicKeyTypeFromSignedCertBlob(cert->data) : CertPubKeyType_Count);
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetPublicKeySizeFromCertificate(Certificate *cert)
|
||||
{
|
||||
return (certIsValidCertificate(cert) ? certGetPublicKeySizeFromSignedCertBlob(cert->data) : 0);
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetPublicKeyBlockSizeFromCertificate(Certificate *cert)
|
||||
{
|
||||
return (certIsValidCertificate(cert) ? certGetPublicKeyBlockSizeFromSignedCertBlob(cert->data) : 0);
|
||||
}
|
||||
|
||||
NX_INLINE u8 *certGetPublicKeyFromCertificate(Certificate *cert)
|
||||
{
|
||||
return (certIsValidCertificate(cert) ? certGetPublicKeyFromSignedCertBlob(cert->data) : NULL);
|
||||
}
|
||||
|
||||
NX_INLINE u8 *certGetPublicExponentFromCertificate(Certificate *cert)
|
||||
{
|
||||
return (certIsValidCertificate(cert) ? certGetPublicExponentFromSignedCertBlob(cert->data) : NULL);
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetHashAreaSizeFromCertificate(Certificate *cert)
|
||||
{
|
||||
return (certIsValidCertificate(cert) ? certGetSignedCertBlobHashAreaSize(cert->data) : 0);
|
||||
}
|
||||
|
||||
/// Helper inline functions for CertificateChain elements.
|
||||
|
||||
NX_INLINE void certFreeCertificateChain(CertificateChain *chain)
|
||||
{
|
||||
|
@ -163,61 +284,6 @@ NX_INLINE void certFreeCertificateChain(CertificateChain *chain)
|
|||
memset(chain, 0, sizeof(CertificateChain));
|
||||
}
|
||||
|
||||
NX_INLINE CertCommonBlock *certGetCommonBlock(void *buf)
|
||||
{
|
||||
return (CertCommonBlock*)signatureGetPayload(buf, true);
|
||||
}
|
||||
|
||||
NX_INLINE bool certIsValidPublicKeyType(u32 type)
|
||||
{
|
||||
return (type == CertPubKeyType_Rsa4096 || type == CertPubKeyType_Rsa2048 || type == CertPubKeyType_Ecc480);
|
||||
}
|
||||
|
||||
NX_INLINE u8 *certGetPublicKey(CertCommonBlock *cert_common_block)
|
||||
{
|
||||
return ((cert_common_block != NULL && certIsValidPublicKeyType(__builtin_bswap32(cert_common_block->pub_key_type))) ? ((u8*)cert_common_block + sizeof(CertCommonBlock)) : NULL);
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetPublicKeySize(u32 type)
|
||||
{
|
||||
return (u64)(type == CertPubKeyType_Rsa4096 ? MEMBER_SIZE(CertPublicKeyBlockRsa4096, public_key) : \
|
||||
(type == CertPubKeyType_Rsa2048 ? MEMBER_SIZE(CertPublicKeyBlockRsa2048, public_key) : \
|
||||
(type == CertPubKeyType_Ecc480 ? MEMBER_SIZE(CertPublicKeyBlockEcc480, public_key) : 0)));
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetPublicKeyBlockSize(u32 type)
|
||||
{
|
||||
return (u64)(type == CertPubKeyType_Rsa4096 ? sizeof(CertPublicKeyBlockRsa4096) : \
|
||||
(type == CertPubKeyType_Rsa2048 ? sizeof(CertPublicKeyBlockRsa2048) : \
|
||||
(type == CertPubKeyType_Ecc480 ? sizeof(CertPublicKeyBlockEcc480) : 0)));
|
||||
}
|
||||
|
||||
NX_INLINE u32 certGetPublicExponent(CertCommonBlock *cert_common_block)
|
||||
{
|
||||
u8 *public_key = certGetPublicKey(cert_common_block);
|
||||
return ((cert_common_block != NULL && public_key != NULL) ? __builtin_bswap32(*((u32*)(public_key + certGetPublicKeySize(__builtin_bswap32(cert_common_block->pub_key_type))))) : 0);
|
||||
}
|
||||
|
||||
NX_INLINE bool certIsValidCertificate(void *buf)
|
||||
{
|
||||
CertCommonBlock *cert_common_block = certGetCommonBlock(buf);
|
||||
return (cert_common_block != NULL && certIsValidPublicKeyType(__builtin_bswap32(cert_common_block->pub_key_type)));
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetSignedCertificateSize(void *buf)
|
||||
{
|
||||
if (!certIsValidCertificate(buf)) return 0;
|
||||
CertCommonBlock *cert_common_block = certGetCommonBlock(buf);
|
||||
return (signatureGetBlockSize(signatureGetSigType(buf, true)) + (u64)sizeof(CertCommonBlock) + certGetPublicKeyBlockSize(__builtin_bswap32(cert_common_block->pub_key_type)));
|
||||
}
|
||||
|
||||
NX_INLINE u64 certGetSignedCertificateHashAreaSize(void *buf)
|
||||
{
|
||||
if (!certIsValidCertificate(buf)) return 0;
|
||||
CertCommonBlock *cert_common_block = certGetCommonBlock(buf);
|
||||
return ((u64)sizeof(CertCommonBlock) + certGetPublicKeyBlockSize(__builtin_bswap32(cert_common_block->pub_key_type)));
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -95,7 +95,7 @@ typedef enum {
|
|||
NcaKeyGeneration_Since1400NUP = 14, ///< 14.0.0 - 14.1.2.
|
||||
NcaKeyGeneration_Since1500NUP = 15, ///< 15.0.0 - 15.0.1.
|
||||
NcaKeyGeneration_Since1600NUP = 16, ///< 16.0.0 - 16.1.0.
|
||||
NcaKeyGeneration_Since1700NUP = 17, ///< 17.0.0.
|
||||
NcaKeyGeneration_Since1700NUP = 17, ///< 17.0.0+.
|
||||
NcaKeyGeneration_Current = NcaKeyGeneration_Since1700NUP,
|
||||
NcaKeyGeneration_Max = 32
|
||||
} NcaKeyGeneration;
|
||||
|
@ -111,7 +111,7 @@ typedef enum {
|
|||
/// TODO: update on signature keygen changes.
|
||||
typedef enum {
|
||||
NcaSignatureKeyGeneration_Since100NUP = 0, ///< 1.0.0 - 8.1.1.
|
||||
NcaSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0 - 17.0.0.
|
||||
NcaSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0+.
|
||||
NcaSignatureKeyGeneration_Current = NcaSignatureKeyGeneration_Since900NUP,
|
||||
NcaSignatureKeyGeneration_Max = (NcaSignatureKeyGeneration_Current + 1)
|
||||
} NcaSignatureKeyGeneration;
|
||||
|
|
|
@ -43,7 +43,7 @@ extern "C" {
|
|||
/// TODO: update on signature keygen changes.
|
||||
typedef enum {
|
||||
NpdmSignatureKeyGeneration_Since100NUP = 0, ///< 1.0.0 - 8.1.1.
|
||||
NpdmSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0 - 17.0.0.
|
||||
NpdmSignatureKeyGeneration_Since900NUP = 1, ///< 9.0.0+.
|
||||
NpdmSignatureKeyGeneration_Current = NpdmSignatureKeyGeneration_Since900NUP,
|
||||
NpdmSignatureKeyGeneration_Max = (NpdmSignatureKeyGeneration_Current + 1)
|
||||
} NpdmSignatureKeyGeneration;
|
||||
|
|
|
@ -36,11 +36,17 @@ extern "C" {
|
|||
#define RSA2048_PUBKEY_SIZE RSA2048_BYTES
|
||||
|
||||
/// Verifies a RSA-2048-PSS with SHA-256 signature.
|
||||
/// Suitable for NCA and NPDM signatures.
|
||||
/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
|
||||
bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size);
|
||||
|
||||
/// Verifies a RSA-2048-PKCS#1 v1.5 with SHA-256 signature.
|
||||
/// Suitable for ticket and certificate chain signatures.
|
||||
/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
|
||||
bool rsa2048VerifySha256BasedPkcs1v15Signature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size);
|
||||
|
||||
/// Performs RSA-2048-OAEP decryption.
|
||||
/// Suitable to decrypt the titlekey block from tickets with personalized crypto.
|
||||
/// Suitable to decrypt the titlekey block from personalized tickets.
|
||||
/// The provided signature and modulus must have sizes of at least RSA2048_SIG_SIZE and RSA2048_PUBKEY_SIZE, respectively.
|
||||
/// 'label' and 'label_size' arguments are optional -- if not needed, these may be set to NULL and 0, respectively.
|
||||
bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, const void *private_exponent, \
|
||||
|
|
|
@ -29,13 +29,13 @@ extern "C" {
|
|||
#endif
|
||||
|
||||
typedef enum {
|
||||
SignatureType_Rsa4096Sha1 = 0x10000,
|
||||
SignatureType_Rsa2048Sha1 = 0x10001,
|
||||
SignatureType_Ecc480Sha1 = 0x10002,
|
||||
SignatureType_Rsa4096Sha256 = 0x10003,
|
||||
SignatureType_Rsa2048Sha256 = 0x10004,
|
||||
SignatureType_Ecc480Sha256 = 0x10005,
|
||||
SignatureType_Hmac160Sha1 = 0x10006
|
||||
SignatureType_Rsa4096Sha1 = 0x10000, ///< RSA-4096 PKCS#1 v1.5 + SHA-1.
|
||||
SignatureType_Rsa2048Sha1 = 0x10001, ///< RSA-2048 PKCS#1 v1.5 + SHA-1.
|
||||
SignatureType_Ecc480Sha1 = 0x10002, ///< Unpadded ECDSA + SHA-1.
|
||||
SignatureType_Rsa4096Sha256 = 0x10003, ///< RSA-4096 PKCS#1 v1.5 + SHA-256.
|
||||
SignatureType_Rsa2048Sha256 = 0x10004, ///< RSA-2048 PKCS#1 v1.5 + SHA-256.
|
||||
SignatureType_Ecc480Sha256 = 0x10005, ///< Unpadded ECDSA + SHA-256.
|
||||
SignatureType_Hmac160Sha1 = 0x10006 ///< HMAC-SHA1-160.
|
||||
} SignatureType;
|
||||
|
||||
typedef struct {
|
||||
|
@ -72,25 +72,14 @@ NXDT_ASSERT(SignatureBlockHmac160, 0x40);
|
|||
|
||||
/// Helper inline functions.
|
||||
|
||||
NX_INLINE u32 signatureGetSigType(void *buf, bool byteswap)
|
||||
{
|
||||
if (!buf) return 0;
|
||||
return (byteswap ? __builtin_bswap32(*((u32*)buf)) : *((u32*)buf));
|
||||
}
|
||||
|
||||
NX_INLINE bool signatureIsValidSigType(u32 type)
|
||||
NX_INLINE bool signatureIsValidType(u32 type)
|
||||
{
|
||||
return (type == SignatureType_Rsa4096Sha1 || type == SignatureType_Rsa2048Sha1 || type == SignatureType_Ecc480Sha1 || \
|
||||
type == SignatureType_Rsa4096Sha256 || type == SignatureType_Rsa2048Sha256 || type == SignatureType_Ecc480Sha256 || \
|
||||
type == SignatureType_Hmac160Sha1);
|
||||
}
|
||||
|
||||
NX_INLINE u8 *signatureGetSig(void *buf)
|
||||
{
|
||||
return (buf ? ((u8*)buf + 4) : NULL);
|
||||
}
|
||||
|
||||
NX_INLINE u64 signatureGetSigSize(u32 type)
|
||||
NX_INLINE u64 signatureGetSigSizeByType(u32 type)
|
||||
{
|
||||
return (u64)((type == SignatureType_Rsa4096Sha1 || type == SignatureType_Rsa4096Sha256) ? MEMBER_SIZE(SignatureBlockRsa4096, signature) : \
|
||||
((type == SignatureType_Rsa2048Sha1 || type == SignatureType_Rsa2048Sha256) ? MEMBER_SIZE(SignatureBlockRsa2048, signature) : \
|
||||
|
@ -98,7 +87,7 @@ NX_INLINE u64 signatureGetSigSize(u32 type)
|
|||
(type == SignatureType_Hmac160Sha1 ? MEMBER_SIZE(SignatureBlockHmac160, signature) : 0))));
|
||||
}
|
||||
|
||||
NX_INLINE u64 signatureGetBlockSize(u32 type)
|
||||
NX_INLINE u64 signatureGetBlockSizeByType(u32 type)
|
||||
{
|
||||
return (u64)((type == SignatureType_Rsa4096Sha1 || type == SignatureType_Rsa4096Sha256) ? sizeof(SignatureBlockRsa4096) : \
|
||||
((type == SignatureType_Rsa2048Sha1 || type == SignatureType_Rsa2048Sha256) ? sizeof(SignatureBlockRsa2048) : \
|
||||
|
@ -106,11 +95,27 @@ NX_INLINE u64 signatureGetBlockSize(u32 type)
|
|||
(type == SignatureType_Hmac160Sha1 ? sizeof(SignatureBlockHmac160) : 0))));
|
||||
}
|
||||
|
||||
NX_INLINE void *signatureGetPayload(void *buf, bool big_endian_sig_type)
|
||||
NX_INLINE u32 signatureGetTypeFromSignedBlob(void *buf, bool big_endian_sig_type)
|
||||
{
|
||||
if (!buf) return 0;
|
||||
return (big_endian_sig_type ? __builtin_bswap32(*((u32*)buf)) : *((u32*)buf));
|
||||
}
|
||||
|
||||
NX_INLINE u8 *signatureGetSigFromSignedBlob(void *buf)
|
||||
{
|
||||
return (buf ? ((u8*)buf + 4) : NULL);
|
||||
}
|
||||
|
||||
NX_INLINE u64 signatureGetBlockSizeFromSignedBlob(void *buf, bool big_endian_sig_type)
|
||||
{
|
||||
return signatureGetBlockSizeByType(signatureGetTypeFromSignedBlob(buf, big_endian_sig_type));
|
||||
}
|
||||
|
||||
NX_INLINE void *signatureGetPayloadFromSignedBlob(void *buf, bool big_endian_sig_type)
|
||||
{
|
||||
if (!buf) return NULL;
|
||||
u32 sig_type = signatureGetSigType(buf, big_endian_sig_type);
|
||||
return (signatureIsValidSigType(sig_type) ? (void*)((u8*)buf + signatureGetBlockSize(sig_type)) : NULL);
|
||||
u32 sig_type = signatureGetTypeFromSignedBlob(buf, big_endian_sig_type);
|
||||
return (signatureIsValidType(sig_type) ? (void*)((u8*)buf + signatureGetBlockSizeByType(sig_type)) : NULL);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SIGNED_TIK_MAX_SIZE 0x400 /* Max ticket entry size in the ES ticket system savedata file. */
|
||||
#define SIGNED_TIK_MIN_SIZE sizeof(TikSigHmac160) /* Assuming no ESV1/ESV2 records are available. */
|
||||
#define SIGNED_TIK_MAX_SIZE 0x400 /* Max ticket entry size in the ES ticket system savedata file. */
|
||||
|
||||
#define GENERATE_TIK_STRUCT(sigtype, tiksize) \
|
||||
typedef struct { \
|
||||
|
@ -59,12 +59,12 @@ typedef enum {
|
|||
|
||||
typedef enum {
|
||||
TikPropertyMask_None = 0,
|
||||
TikPropertyMask_PreInstallation = BIT(0),
|
||||
TikPropertyMask_SharedTitle = BIT(1),
|
||||
TikPropertyMask_AllContents = BIT(2),
|
||||
TikPropertyMask_DeviceLinkIndepedent = BIT(3),
|
||||
TikPropertyMask_Volatile = BIT(4), ///< Used to determine if the ticket copy inside ticket.bin should be encrypted or not.
|
||||
TikPropertyMask_ELicenseRequired = BIT(5), ///< Used to determine if the console should connect to the Internet to perform elicense verification.
|
||||
TikPropertyMask_PreInstallation = BIT(0), ///< Determines if the title comes pre-installed on the device. Most likely unused -- a remnant from previous ticket formats.
|
||||
TikPropertyMask_SharedTitle = BIT(1), ///< Determines if the title holds shared contents only. Most likely unused -- a remnant from previous ticket formats.
|
||||
TikPropertyMask_AllContents = BIT(2), ///< Determines if the content index mask shall be bypassed. Most likely unused -- a remnant from previous ticket formats.
|
||||
TikPropertyMask_DeviceLinkIndepedent = BIT(3), ///< Determines if the console should *not* connect to the Internet to verify if the title's being used by the primary console.
|
||||
TikPropertyMask_Volatile = BIT(4), ///< Determines if the ticket copy inside ticket.bin should be encrypted or not.
|
||||
TikPropertyMask_ELicenseRequired = BIT(5), ///< Determines if the console should connect to the Internet to perform license verification.
|
||||
TikPropertyMask_Count = 6 ///< Total values supported by this enum.
|
||||
} TikPropertyMask;
|
||||
|
||||
|
@ -174,14 +174,18 @@ typedef struct {
|
|||
u8 type; ///< TikType.
|
||||
u64 size; ///< Raw ticket size.
|
||||
u8 data[SIGNED_TIK_MAX_SIZE]; ///< Raw ticket data.
|
||||
u8 key_generation; ///< NcaKeyGeneration.
|
||||
u8 enc_titlekey[0x10]; ///< Titlekey with titlekek crypto (RSA-OAEP unwrapped if dealing with a TikTitleKeyType_Personalized ticket).
|
||||
char enc_titlekey_str[0x21]; ///< Character string representation of enc_titlekey.
|
||||
u8 dec_titlekey[0x10]; ///< Titlekey without titlekek crypto. Ready to use for NCA FS section decryption.
|
||||
char dec_titlekey_str[0x21]; ///< Character string representation of dec_titlekey.
|
||||
char rights_id_str[0x21]; ///< Character string representation of the rights ID from the ticket.
|
||||
} Ticket;
|
||||
|
||||
/// Retrieves a ticket from either the ES ticket system savedata file (eMMC BIS System partition) or the secure hash FS partition from an inserted gamecard, using a Rights ID value.
|
||||
/// Titlekey is also RSA-OAEP unwrapped (if needed) and titlekek decrypted right away.
|
||||
bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gamecard);
|
||||
/// Retrieves a ticket from either the ES ticket system savedata file (eMMC BIS System partition) or the secure Hash FS partition from an inserted gamecard.
|
||||
/// Both the input rights ID and key generation values must have been retrieved from a NCA that depends on the desired ticket.
|
||||
/// Titlekey is also RSA-OAEP unwrapped (if needed) and titlekek-decrypted right away.
|
||||
bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, u8 key_generation, bool use_gamecard);
|
||||
|
||||
/// Converts a TikTitleKeyType_Personalized ticket into a TikTitleKeyType_Common ticket and optionally 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.
|
||||
|
@ -189,15 +193,16 @@ bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gam
|
|||
/// 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.
|
||||
/// Helper inline functions for signed ticket blobs.
|
||||
|
||||
NX_INLINE TikCommonBlock *tikGetCommonBlock(void *buf)
|
||||
NX_INLINE TikCommonBlock *tikGetCommonBlockFromSignedTicketBlob(void *buf)
|
||||
{
|
||||
return (TikCommonBlock*)signatureGetPayload(buf, false);
|
||||
return (TikCommonBlock*)signatureGetPayloadFromSignedBlob(buf, false);
|
||||
}
|
||||
|
||||
NX_INLINE u64 tikGetTicketSectionRecordsBlockSize(TikCommonBlock *tik_common_block)
|
||||
NX_INLINE u64 tikGetSectionRecordsSizeFromSignedTicketBlob(void *buf)
|
||||
{
|
||||
TikCommonBlock *tik_common_block = tikGetCommonBlockFromSignedTicketBlob(buf);
|
||||
if (!tik_common_block) return 0;
|
||||
|
||||
u64 offset = sizeof(TikCommonBlock), out_size = 0;
|
||||
|
@ -212,26 +217,43 @@ NX_INLINE u64 tikGetTicketSectionRecordsBlockSize(TikCommonBlock *tik_common_blo
|
|||
return out_size;
|
||||
}
|
||||
|
||||
NX_INLINE bool tikIsValidTicket(void *buf)
|
||||
NX_INLINE bool tikIsValidSignedTicketBlob(void *buf)
|
||||
{
|
||||
u64 ticket_size = (signatureGetBlockSize(signatureGetSigType(buf, false)) + sizeof(TikCommonBlock));
|
||||
return (ticket_size > sizeof(TikCommonBlock) && (ticket_size + tikGetTicketSectionRecordsBlockSize(tikGetCommonBlock(buf))) <= SIGNED_TIK_MAX_SIZE);
|
||||
u64 ticket_size = (signatureGetBlockSizeFromSignedBlob(buf, false) + sizeof(TikCommonBlock));
|
||||
return (ticket_size > sizeof(TikCommonBlock) && (ticket_size + tikGetSectionRecordsSizeFromSignedTicketBlob(buf)) <= SIGNED_TIK_MAX_SIZE);
|
||||
}
|
||||
|
||||
NX_INLINE u64 tikGetSignedTicketSize(void *buf)
|
||||
NX_INLINE u64 tikGetSignedTicketBlobSize(void *buf)
|
||||
{
|
||||
return (tikIsValidTicket(buf) ? (signatureGetBlockSize(signatureGetSigType(buf, false)) + sizeof(TikCommonBlock) + tikGetTicketSectionRecordsBlockSize(tikGetCommonBlock(buf))) : 0);
|
||||
return (tikIsValidSignedTicketBlob(buf) ? (signatureGetBlockSizeFromSignedBlob(buf, false) + sizeof(TikCommonBlock) + tikGetSectionRecordsSizeFromSignedTicketBlob(buf)) : 0);
|
||||
}
|
||||
|
||||
NX_INLINE u64 tikGetSignedTicketHashAreaSize(void *buf)
|
||||
NX_INLINE u64 tikGetSignedTicketBlobHashAreaSize(void *buf)
|
||||
{
|
||||
return (tikIsValidTicket(buf) ? (sizeof(TikCommonBlock) + tikGetTicketSectionRecordsBlockSize(tikGetCommonBlock(buf))) : 0);
|
||||
return (tikIsValidSignedTicketBlob(buf) ? (sizeof(TikCommonBlock) + tikGetSectionRecordsSizeFromSignedTicketBlob(buf)) : 0);
|
||||
}
|
||||
|
||||
/// Helper inline functions for Ticket elements.
|
||||
|
||||
NX_INLINE bool tikIsValidTicket(Ticket *tik)
|
||||
{
|
||||
return (tik && tik->type > TikType_None && tik->type < TikType_Count && tik->size >= SIGNED_TIK_MIN_SIZE && tik->size <= SIGNED_TIK_MAX_SIZE && tikIsValidSignedTicketBlob(tik->data));
|
||||
}
|
||||
|
||||
NX_INLINE TikCommonBlock *tikGetCommonBlockFromTicket(Ticket *tik)
|
||||
{
|
||||
return (tikIsValidTicket(tik) ? tikGetCommonBlockFromSignedTicketBlob(tik->data) : NULL);
|
||||
}
|
||||
|
||||
NX_INLINE bool tikIsPersonalizedTicket(Ticket *tik)
|
||||
{
|
||||
TikCommonBlock *tik_common_block = tikGetCommonBlock(tik->data);
|
||||
return (tik_common_block != NULL && tik_common_block->titlekey_type == TikTitleKeyType_Personalized);
|
||||
TikCommonBlock *tik_common_block = tikGetCommonBlockFromTicket(tik);
|
||||
return (tik_common_block ? (tik_common_block->titlekey_type == TikTitleKeyType_Personalized) : false);
|
||||
}
|
||||
|
||||
NX_INLINE u64 tikGetHashAreaSizeFromTicket(Ticket *tik)
|
||||
{
|
||||
return (tikIsValidTicket(tik) ? tikGetSignedTicketBlobHashAreaSize(tik->data) : 0);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -46,7 +46,6 @@ static u8 certGetCertificateType(void *data, u64 data_size);
|
|||
static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer);
|
||||
static u32 certGetCertificateCountInSignatureIssuer(const char *issuer);
|
||||
|
||||
static u64 certCalculateRawCertificateChainSize(const CertificateChain *chain);
|
||||
static void certCopyCertificateChainDataToMemoryBuffer(void *dst, const CertificateChain *chain);
|
||||
|
||||
bool certRetrieveCertificateByName(Certificate *dst, const char *name)
|
||||
|
@ -99,25 +98,27 @@ u8 *certGenerateRawCertificateChainBySignatureIssuer(const char *issuer, u64 *ou
|
|||
|
||||
CertificateChain chain = {0};
|
||||
u8 *raw_chain = NULL;
|
||||
u64 raw_chain_size = 0;
|
||||
|
||||
/* Get full certificate chain using the provided issuer. */
|
||||
if (!certRetrieveCertificateChainBySignatureIssuer(&chain, issuer))
|
||||
{
|
||||
LOG_MSG_ERROR("Error retrieving certificate chain for \"%s\"!", issuer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
raw_chain_size = certCalculateRawCertificateChainSize(&chain);
|
||||
|
||||
raw_chain = malloc(raw_chain_size);
|
||||
if (!raw_chain)
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to allocate memory for raw \"%s\" certificate chain! (0x%lX).", issuer, raw_chain_size);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Allocate memory for the raw certificate chain. */
|
||||
raw_chain = malloc(chain.size);
|
||||
if (!raw_chain)
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to allocate memory for raw \"%s\" certificate chain! (0x%lX).", issuer, chain.size);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Copy all certificates to the allocated buffer. */
|
||||
certCopyCertificateChainDataToMemoryBuffer(raw_chain, &chain);
|
||||
*out_size = raw_chain_size;
|
||||
|
||||
/* Update output size. */
|
||||
*out_size = chain.size;
|
||||
|
||||
end:
|
||||
certFreeCertificateChain(&chain);
|
||||
|
@ -138,34 +139,40 @@ u8 *certRetrieveRawCertificateChainFromGameCardByRightsId(const FsRightsId *id,
|
|||
u8 *raw_chain = NULL;
|
||||
bool success = false;
|
||||
|
||||
/* Generate certificate chain filename. */
|
||||
utilsGenerateHexStringFromData(raw_chain_filename, sizeof(raw_chain_filename), id->c, sizeof(id->c), false);
|
||||
strcat(raw_chain_filename, ".cert");
|
||||
|
||||
/* Get certificate chain entry info. */
|
||||
if (!gamecardGetHashFileSystemEntryInfoByName(HashFileSystemPartitionType_Secure, raw_chain_filename, &raw_chain_offset, &raw_chain_size))
|
||||
{
|
||||
LOG_MSG_ERROR("Error retrieving offset and size for \"%s\" entry in secure hash FS partition!", raw_chain_filename);
|
||||
return NULL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Validate certificate chain size. */
|
||||
if (raw_chain_size < SIGNED_CERT_MIN_SIZE)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid size for \"%s\"! (0x%lX).", raw_chain_filename, raw_chain_size);
|
||||
return NULL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Allocate memory for the certificate chain. */
|
||||
raw_chain = malloc(raw_chain_size);
|
||||
if (!raw_chain)
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to allocate memory for raw \"%s\" certificate chain! (0x%lX).", raw_chain_filename, raw_chain_size);
|
||||
return NULL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Read raw certificate chain from the inserted gamecard. */
|
||||
if (!gamecardReadStorage(raw_chain, raw_chain_size, raw_chain_offset))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to read \"%s\" data from the inserted gamecard!", raw_chain_filename);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Update output. */
|
||||
*out_size = raw_chain_size;
|
||||
success = true;
|
||||
|
||||
|
@ -202,9 +209,9 @@ static void certCloseEsCertSaveFile(void)
|
|||
|
||||
static bool _certRetrieveCertificateByName(Certificate *dst, const char *name)
|
||||
{
|
||||
if (!g_esCertSaveCtx)
|
||||
if (!g_esCertSaveCtx || !dst || !name || !*name)
|
||||
{
|
||||
LOG_MSG_ERROR("ES certificate savefile not opened!");
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -212,22 +219,27 @@ static bool _certRetrieveCertificateByName(Certificate *dst, const char *name)
|
|||
char cert_path[SAVE_FS_LIST_MAX_NAME_LENGTH] = {0};
|
||||
allocation_table_storage_ctx_t fat_storage = {0};
|
||||
|
||||
snprintf(cert_path, SAVE_FS_LIST_MAX_NAME_LENGTH, CERT_SAVEFILE_STORAGE_BASE_PATH "%s", name);
|
||||
/* Generate path for the requested certificate within the ES certificate save file. */
|
||||
snprintf(cert_path, MAX_ELEMENTS(cert_path), CERT_SAVEFILE_STORAGE_BASE_PATH "%s", name);
|
||||
|
||||
/* Get FAT storage info for the certificate. */
|
||||
if (!save_get_fat_storage_from_file_entry_by_path(g_esCertSaveCtx, cert_path, &fat_storage, &cert_size))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to locate certificate \"%s\" in ES certificate system save!", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Validate certificate size. */
|
||||
if (cert_size < SIGNED_CERT_MIN_SIZE || cert_size > SIGNED_CERT_MAX_SIZE)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid size for certificate \"%s\"! (0x%lX).", name, cert_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Update output size. */
|
||||
dst->size = cert_size;
|
||||
|
||||
/* Read certificate data. */
|
||||
u64 br = save_allocation_table_storage_read(&fat_storage, dst->data, 0, dst->size);
|
||||
if (br != dst->size)
|
||||
{
|
||||
|
@ -235,8 +247,9 @@ static bool _certRetrieveCertificateByName(Certificate *dst, const char *name)
|
|||
return false;
|
||||
}
|
||||
|
||||
/* Get certificate type. */
|
||||
dst->type = certGetCertificateType(dst->data, dst->size);
|
||||
if (dst->type == CertType_None)
|
||||
if (dst->type == CertType_None || dst->type >= CertType_Count)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid certificate type for \"%s\"!", name);
|
||||
return false;
|
||||
|
@ -247,26 +260,26 @@ static bool _certRetrieveCertificateByName(Certificate *dst, const char *name)
|
|||
|
||||
static u8 certGetCertificateType(void *data, u64 data_size)
|
||||
{
|
||||
CertCommonBlock *cert_common_block = NULL;
|
||||
u32 sig_type = 0, pub_key_type = 0;
|
||||
u64 signed_cert_size = 0;
|
||||
u8 type = CertType_None;
|
||||
|
||||
if (!data || data_size < SIGNED_CERT_MIN_SIZE || data_size > SIGNED_CERT_MAX_SIZE)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return type;
|
||||
return CertType_None;
|
||||
}
|
||||
|
||||
if (!(cert_common_block = certGetCommonBlock(data)) || !(signed_cert_size = certGetSignedCertificateSize(data)) || signed_cert_size > data_size)
|
||||
u32 sig_type = 0, pub_key_type = 0;
|
||||
u8 type = CertType_None;
|
||||
|
||||
/* Get signature and public key types. */
|
||||
sig_type = signatureGetTypeFromSignedBlob(data, true);
|
||||
pub_key_type = certGetPublicKeyTypeFromSignedCertBlob(data);
|
||||
|
||||
if (!signatureIsValidType(sig_type) || !certIsValidPublicKeyType(pub_key_type))
|
||||
{
|
||||
LOG_MSG_ERROR("Input buffer doesn't hold a valid signed certificate!");
|
||||
return type;
|
||||
goto end;
|
||||
}
|
||||
|
||||
sig_type = signatureGetSigType(data, true);
|
||||
pub_key_type = __builtin_bswap32(cert_common_block->pub_key_type);
|
||||
|
||||
/* Determine certificate type. */
|
||||
switch(sig_type)
|
||||
{
|
||||
case SignatureType_Rsa4096Sha1:
|
||||
|
@ -288,53 +301,67 @@ static u8 certGetCertificateType(void *data, u64 data_size)
|
|||
break;
|
||||
}
|
||||
|
||||
end:
|
||||
return type;
|
||||
}
|
||||
|
||||
static bool _certRetrieveCertificateChainBySignatureIssuer(CertificateChain *dst, const char *issuer)
|
||||
{
|
||||
if (!g_esCertSaveCtx)
|
||||
if (!dst || !issuer || !*issuer)
|
||||
{
|
||||
LOG_MSG_ERROR("ES certificate savefile not opened!");
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 i = 0;
|
||||
char issuer_copy[0x40] = {0}, *pch = NULL, *state = NULL;
|
||||
bool success = true;
|
||||
bool success = false;
|
||||
|
||||
/* Free output context beforehand. */
|
||||
certFreeCertificateChain(dst);
|
||||
|
||||
/* Determine how many certificate we need to retrieve. */
|
||||
dst->count = certGetCertificateCountInSignatureIssuer(issuer);
|
||||
if (!dst->count)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid signature issuer string!");
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Allocate memory for all the certificates. */
|
||||
dst->certs = calloc(dst->count, sizeof(Certificate));
|
||||
if (!dst->certs)
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to allocate memory for the certificate chain! (0x%lX).", dst->count * sizeof(Certificate));
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Copy string to avoid problems with strtok_r(). */
|
||||
/* The "Root-" parent from the issuer string is skipped. */
|
||||
snprintf(issuer_copy, sizeof(issuer_copy), "%s", issuer + 5);
|
||||
|
||||
/* Collect all required certificates. */
|
||||
pch = strtok_r(issuer_copy, "-", &state);
|
||||
while(pch)
|
||||
{
|
||||
/* Get current certificate. */
|
||||
if (!_certRetrieveCertificateByName(&(dst->certs[i]), pch))
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to retrieve certificate \"%s\"!", pch);
|
||||
success = false;
|
||||
break;
|
||||
goto end;
|
||||
}
|
||||
|
||||
i++;
|
||||
/* Update certificate chain size and current index. */
|
||||
dst->size += dst->certs[i++].size;
|
||||
|
||||
/* Get name for the next certificate. */
|
||||
pch = strtok_r(NULL, "-", &state);
|
||||
}
|
||||
|
||||
/* Update output value. */
|
||||
success = true;
|
||||
|
||||
end:
|
||||
if (!success) certFreeCertificateChain(dst);
|
||||
|
||||
return success;
|
||||
|
@ -361,15 +388,6 @@ static u32 certGetCertificateCountInSignatureIssuer(const char *issuer)
|
|||
return count;
|
||||
}
|
||||
|
||||
static u64 certCalculateRawCertificateChainSize(const CertificateChain *chain)
|
||||
{
|
||||
if (!chain || !chain->count || !chain->certs) return 0;
|
||||
|
||||
u64 chain_size = 0;
|
||||
for(u32 i = 0; i < chain->count; i++) chain_size += chain->certs[i].size;
|
||||
return chain_size;
|
||||
}
|
||||
|
||||
static void certCopyCertificateChainDataToMemoryBuffer(void *dst, const CertificateChain *chain)
|
||||
{
|
||||
if (!chain || !chain->count || !chain->certs) return;
|
||||
|
|
|
@ -194,7 +194,7 @@ const u8 *keysGetNcaKeyAreaEncryptionKey(u8 kaek_index, u8 key_generation)
|
|||
goto end;
|
||||
}
|
||||
|
||||
if (key_generation >= NcaKeyGeneration_Max)
|
||||
if (key_generation > NcaKeyGeneration_Max)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid key generation value! (0x%02X).", key_generation);
|
||||
goto end;
|
||||
|
@ -258,7 +258,7 @@ const u8 *keysGetTicketCommonKey(u8 key_generation)
|
|||
const u8 *ret = NULL;
|
||||
u8 key_gen_val = (key_generation ? (key_generation - 1) : key_generation);
|
||||
|
||||
if (key_generation >= NcaKeyGeneration_Max)
|
||||
if (key_generation > NcaKeyGeneration_Max)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid key generation value! (0x%02X).", key_generation);
|
||||
goto end;
|
||||
|
|
|
@ -244,12 +244,13 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
|
|||
|
||||
/* Retrieve ticket. */
|
||||
/* This will return true if it has already been retrieved. */
|
||||
if (tikRetrieveTicketByRightsId(usable_tik, &(out->header.rights_id), out->storage_id == NcmStorageId_GameCard))
|
||||
if (tikRetrieveTicketByRightsId(usable_tik, &(out->header.rights_id), out->key_generation, out->storage_id == NcmStorageId_GameCard))
|
||||
{
|
||||
/* Copy decrypted titlekey. */
|
||||
memcpy(out->titlekey, usable_tik->dec_titlekey, 0x10);
|
||||
memcpy(out->titlekey, usable_tik->dec_titlekey, sizeof(usable_tik->dec_titlekey));
|
||||
out->titlekey_retrieved = true;
|
||||
} else {
|
||||
/* We must proceed even if we have no ticket. The user may just want to copy a raw NCA. */
|
||||
LOG_MSG_ERROR("Error retrieving ticket for NCA \"%s\"!", out->content_id_str);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,47 +28,19 @@
|
|||
#include <mbedtls/ctr_drbg.h>
|
||||
#include <mbedtls/pk.h>
|
||||
|
||||
/* Function prototypes. */
|
||||
|
||||
static bool rsa2048VerifySha256BasedSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, \
|
||||
bool use_pss);
|
||||
|
||||
bool rsa2048VerifySha256BasedPssSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size)
|
||||
{
|
||||
if (!data || !data_size || !signature || !modulus || !public_exponent || !public_exponent_size)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
return rsa2048VerifySha256BasedSignature(data, data_size, signature, modulus, public_exponent, public_exponent_size, true);
|
||||
}
|
||||
|
||||
int mbedtls_ret = 0;
|
||||
mbedtls_rsa_context rsa;
|
||||
u8 hash[SHA256_HASH_SIZE] = {0};
|
||||
bool ret = false;
|
||||
|
||||
/* Initialize RSA context. */
|
||||
mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
|
||||
|
||||
/* Import RSA parameters. */
|
||||
mbedtls_ret = mbedtls_rsa_import_raw(&rsa, (const u8*)modulus, RSA2048_BYTES, NULL, 0, NULL, 0, NULL, 0, (const u8*)public_exponent, public_exponent_size);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("mbedtls_rsa_import_raw failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Calculate SHA-256 checksum for the input data. */
|
||||
sha256CalculateHash(hash, data, data_size);
|
||||
|
||||
/* Verify signature. */
|
||||
mbedtls_ret = mbedtls_rsa_rsassa_pss_verify(&rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, SHA256_HASH_SIZE, hash, (const u8*)signature);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("mbedtls_rsa_rsassa_pss_verify failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
end:
|
||||
mbedtls_rsa_free(&rsa);
|
||||
|
||||
return ret;
|
||||
bool rsa2048VerifySha256BasedPkcs1v15Signature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size)
|
||||
{
|
||||
return rsa2048VerifySha256BasedSignature(data, data_size, signature, modulus, public_exponent, public_exponent_size, false);
|
||||
}
|
||||
|
||||
bool rsa2048OaepDecrypt(void *dst, size_t dst_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, const void *private_exponent, \
|
||||
|
@ -135,3 +107,48 @@ end:
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool rsa2048VerifySha256BasedSignature(const void *data, size_t data_size, const void *signature, const void *modulus, const void *public_exponent, size_t public_exponent_size, \
|
||||
bool use_pss)
|
||||
{
|
||||
if (!data || !data_size || !signature || !modulus || !public_exponent || !public_exponent_size)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
int mbedtls_ret = 0;
|
||||
mbedtls_rsa_context rsa;
|
||||
u8 hash[SHA256_HASH_SIZE] = {0};
|
||||
bool ret = false;
|
||||
|
||||
/* Initialize RSA context. */
|
||||
mbedtls_rsa_init(&rsa, MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
|
||||
|
||||
/* Import RSA parameters. */
|
||||
mbedtls_ret = mbedtls_rsa_import_raw(&rsa, (const u8*)modulus, RSA2048_BYTES, NULL, 0, NULL, 0, NULL, 0, (const u8*)public_exponent, public_exponent_size);
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("mbedtls_rsa_import_raw failed! (%d).", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Calculate SHA-256 checksum for the input data. */
|
||||
sha256CalculateHash(hash, data, data_size);
|
||||
|
||||
/* Verify signature. */
|
||||
mbedtls_ret = (use_pss ? mbedtls_rsa_rsassa_pss_verify(&rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, SHA256_HASH_SIZE, hash, (const u8*)signature) : \
|
||||
mbedtls_rsa_rsassa_pkcs1_v15_verify(&rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, SHA256_HASH_SIZE, hash, (const u8*)signature));
|
||||
if (mbedtls_ret != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("mbedtls_rsa_rsassa_%s_verify failed! (%d).", use_pss ? "pss" : "pkcs1_v15", mbedtls_ret);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = true;
|
||||
|
||||
end:
|
||||
mbedtls_rsa_free(&rsa);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "gamecard.h"
|
||||
#include "mem.h"
|
||||
#include "aes.h"
|
||||
#include "rsa.h"
|
||||
|
||||
#define TIK_COMMON_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e1"
|
||||
#define TIK_PERSONALIZED_SAVEFILE_PATH BIS_SYSTEM_PARTITION_MOUNT_NAME "/save/80000000000000e2"
|
||||
|
@ -36,6 +37,9 @@
|
|||
#define TIK_LIST_STORAGE_PATH "/ticket_list.bin"
|
||||
#define TIK_DB_STORAGE_PATH "/ticket.bin"
|
||||
|
||||
#define TIK_COMMON_CERT_NAME "XS00000020"
|
||||
#define TIK_DEV_CERT_ISSUER "CA00000004"
|
||||
|
||||
/* Type definitions. */
|
||||
|
||||
/// Used to parse ticket_list.bin entries.
|
||||
|
@ -70,6 +74,8 @@ NXDT_ASSERT(TikEsCtrKeyPattern9x, 0x28);
|
|||
|
||||
/* Global variables. */
|
||||
|
||||
static Mutex g_esTikSaveMutex = 0;
|
||||
|
||||
#if LOG_LEVEL <= LOG_LEVEL_ERROR
|
||||
static const char *g_tikTitleKeyTypeStrings[] = {
|
||||
[TikTitleKeyType_Common] = "common",
|
||||
|
@ -89,73 +95,96 @@ static MemoryLocation g_esMemoryLocation = {
|
|||
static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsId *id);
|
||||
static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRightsId *id);
|
||||
|
||||
static bool tikGetEncryptedTitleKeyFromTicket(Ticket *tik);
|
||||
static bool tikFixTamperedCommonTicket(Ticket *tik);
|
||||
static bool tikVerifyRsa2048Sha256Signature(const TikCommonBlock *tik_common_block, u64 hash_area_size, const u8 *signature);
|
||||
|
||||
static bool tikGetEncryptedTitleKey(Ticket *tik);
|
||||
static bool tikGetDecryptedTitleKey(void *dst, const void *src, u8 key_generation);
|
||||
|
||||
static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out);
|
||||
static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count, bool personalized);
|
||||
|
||||
static bool tikGetTicketEntryOffsetFromTicketList(save_ctx_t *save_ctx, u8 *buf, u64 buf_size, const FsRightsId *id, u64 *out_offset, u8 titlekey_type);
|
||||
static bool tikRetrieveTicketEntryFromTicketBin(save_ctx_t *save_ctx, u8 *buf, u64 buf_size, const FsRightsId *id, u64 ticket_offset, u8 titlekey_type);
|
||||
static bool tikGetTicketEntryOffsetFromTicketList(save_ctx_t *save_ctx, u8 *buf, u64 buf_size, const FsRightsId *id, u8 titlekey_type, u64 *out_offset);
|
||||
static bool tikRetrieveTicketEntryFromTicketBin(save_ctx_t *save_ctx, u8 *buf, u64 buf_size, const FsRightsId *id, u8 titlekey_type, u64 ticket_offset);
|
||||
static bool tikDecryptVolatileTicket(u8 *buf, u64 ticket_offset);
|
||||
|
||||
static bool tikGetTicketTypeAndSize(void *data, u64 data_size, u8 *out_type, u64 *out_size);
|
||||
|
||||
bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, bool use_gamecard)
|
||||
bool tikRetrieveTicketByRightsId(Ticket *dst, const FsRightsId *id, u8 key_generation, bool use_gamecard)
|
||||
{
|
||||
if (!dst || !id)
|
||||
if (!dst || !id || key_generation > NcaKeyGeneration_Max)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 key_generation = id->c[0xF];
|
||||
TikCommonBlock *tik_common_block = NULL;
|
||||
bool success = false, tik_retrieved = false;
|
||||
|
||||
/* Check if this ticket has already been retrieved. */
|
||||
if (dst->type > TikType_None && dst->type <= TikType_SigHmac160 && dst->size >= SIGNED_TIK_MIN_SIZE && dst->size <= SIGNED_TIK_MAX_SIZE)
|
||||
tik_common_block = tikGetCommonBlockFromTicket(dst);
|
||||
if (tik_common_block && !memcmp(tik_common_block->rights_id.c, id->c, sizeof(id->c)))
|
||||
{
|
||||
tik_common_block = tikGetCommonBlock(dst->data);
|
||||
if (tik_common_block && !memcmp(tik_common_block->rights_id.c, id->c, 0x10)) return true;
|
||||
success = true;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Clear output ticket. */
|
||||
memset(dst, 0, sizeof(Ticket));
|
||||
|
||||
/* Validate the key generation field within the rights ID. */
|
||||
u8 key_gen_rid = id->c[0xF];
|
||||
bool old_key_gen = (key_generation < NcaKeyGeneration_Since301NUP);
|
||||
|
||||
if ((old_key_gen && key_gen_rid) || (!old_key_gen && key_gen_rid != key_generation))
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid rights ID key generation! Got 0x%02X, expected 0x%02X.", key_gen_rid, old_key_gen ? 0 : key_generation);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Update key generation field. */
|
||||
dst->key_generation = key_generation;
|
||||
|
||||
/* Retrieve ticket data. */
|
||||
bool tik_retrieved = (use_gamecard ? tikRetrieveTicketFromGameCardByRightsId(dst, id) : tikRetrieveTicketFromEsSaveDataByRightsId(dst, id));
|
||||
if (use_gamecard)
|
||||
{
|
||||
tik_retrieved = tikRetrieveTicketFromGameCardByRightsId(dst, id);
|
||||
} else {
|
||||
SCOPED_LOCK(&g_esTikSaveMutex) tik_retrieved = tikRetrieveTicketFromEsSaveDataByRightsId(dst, id);
|
||||
}
|
||||
|
||||
if (!tik_retrieved)
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to retrieve ticket data!");
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Get encrypted titlekey from ticket. */
|
||||
if (!tikGetEncryptedTitleKeyFromTicket(dst))
|
||||
/* Fix tampered common ticket, if needed. */
|
||||
if (!tikFixTamperedCommonTicket(dst)) goto end;
|
||||
|
||||
/* Get encrypted titlekey. */
|
||||
if (!tikGetEncryptedTitleKey(dst))
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to retrieve encrypted titlekey from ticket!");
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Get common ticket block. */
|
||||
tik_common_block = tikGetCommonBlock(dst->data);
|
||||
|
||||
/* Get proper key generation value. */
|
||||
/* Nintendo didn't start putting the key generation value into the rights ID until HOS 3.0.1. */
|
||||
/* If this is the case, we'll just use the key generation value from the common ticket block. */
|
||||
/* However, old custom tools used to wipe the key generation field or save its value to a different offset, so this may fail with titles with custom/modified tickets. */
|
||||
if (key_generation < NcaKeyGeneration_Since301NUP || key_generation > NcaKeyGeneration_Max) key_generation = tik_common_block->key_generation;
|
||||
|
||||
/* Get decrypted titlekey. */
|
||||
if (!tikGetDecryptedTitleKey(dst->dec_titlekey, dst->enc_titlekey, key_generation))
|
||||
if (!(success = tikGetDecryptedTitleKey(dst->dec_titlekey, dst->enc_titlekey, dst->key_generation)))
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to decrypt titlekey!");
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Generate rights ID string. */
|
||||
/* Generate hex strings. */
|
||||
tik_common_block = tikGetCommonBlockFromSignedTicketBlob(dst->data);
|
||||
|
||||
utilsGenerateHexStringFromData(dst->enc_titlekey_str, sizeof(dst->enc_titlekey_str), dst->enc_titlekey, sizeof(dst->enc_titlekey), false);
|
||||
utilsGenerateHexStringFromData(dst->dec_titlekey_str, sizeof(dst->dec_titlekey_str), dst->dec_titlekey, sizeof(dst->dec_titlekey), false);
|
||||
utilsGenerateHexStringFromData(dst->rights_id_str, sizeof(dst->rights_id_str), tik_common_block->rights_id.c, sizeof(tik_common_block->rights_id.c), false);
|
||||
|
||||
return true;
|
||||
end:
|
||||
return success;
|
||||
}
|
||||
|
||||
bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_chain, u64 *out_raw_cert_chain_size)
|
||||
|
@ -168,29 +197,22 @@ bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_c
|
|||
|
||||
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 || (!out_raw_cert_chain && out_raw_cert_chain_size) || \
|
||||
(out_raw_cert_chain && !out_raw_cert_chain_size))
|
||||
if (!(tik_common_block = tikGetCommonBlockFromTicket(tik)) || tik_common_block->titlekey_type != TikTitleKeyType_Personalized || \
|
||||
(!out_raw_cert_chain && out_raw_cert_chain_size) || (out_raw_cert_chain && !out_raw_cert_chain_size))
|
||||
{
|
||||
LOG_MSG_ERROR("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;
|
||||
}
|
||||
dev_cert = (strstr(tik_common_block->issuer, TIK_DEV_CERT_ISSUER) != NULL);
|
||||
sprintf(cert_chain_issuer, "Root-CA%08X-%s", dev_cert ? 4 : 3, TIK_COMMON_CERT_NAME);
|
||||
|
||||
raw_cert_chain = certGenerateRawCertificateChainBySignatureIssuer(cert_chain_issuer, &raw_cert_chain_size);
|
||||
if (!raw_cert_chain)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to generate raw certificate chain for common ticket signature issuer!");
|
||||
|
@ -198,9 +220,9 @@ bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_c
|
|||
}
|
||||
|
||||
/* Wipe signature. */
|
||||
sig_type = signatureGetSigType(tik->data, false);
|
||||
signature = signatureGetSig(tik->data);
|
||||
signature_size = signatureGetSigSize(sig_type);
|
||||
sig_type = signatureGetTypeFromSignedBlob(tik->data, false);
|
||||
signature = signatureGetSigFromSignedBlob(tik->data);
|
||||
signature_size = signatureGetSigSizeByType(sig_type);
|
||||
memset(signature, 0xFF, signature_size);
|
||||
|
||||
/* Change signature issuer. */
|
||||
|
@ -209,14 +231,16 @@ bool tikConvertPersonalizedTicketToCommonTicket(Ticket *tik, u8 **out_raw_cert_c
|
|||
|
||||
/* Wipe the titlekey block and copy the encrypted titlekey to it. */
|
||||
memset(tik_common_block->titlekey_block, 0, sizeof(tik_common_block->titlekey_block));
|
||||
memcpy(tik_common_block->titlekey_block, tik->enc_titlekey, 0x10);
|
||||
memcpy(tik_common_block->titlekey_block, tik->enc_titlekey, sizeof(tik->enc_titlekey));
|
||||
|
||||
/* Update ticket size. */
|
||||
tik->size = (signatureGetBlockSize(sig_type) + sizeof(TikCommonBlock));
|
||||
tik->size = (signatureGetBlockSizeByType(sig_type) + sizeof(TikCommonBlock));
|
||||
|
||||
/* Update the rest of the ticket fields. */
|
||||
tik_common_block->titlekey_type = TikTitleKeyType_Common;
|
||||
tik_common_block->property_mask &= ~(TikPropertyMask_ELicenseRequired | TikPropertyMask_Volatile);
|
||||
tik_common_block->license_type = TikLicenseType_Permanent;
|
||||
tik_common_block->property_mask = TikPropertyMask_None;
|
||||
|
||||
tik_common_block->ticket_id = 0;
|
||||
tik_common_block->device_id = 0;
|
||||
tik_common_block->account_id = 0;
|
||||
|
@ -251,35 +275,37 @@ static bool tikRetrieveTicketFromGameCardByRightsId(Ticket *dst, const FsRightsI
|
|||
|
||||
char tik_filename[0x30] = {0};
|
||||
u64 tik_offset = 0, tik_size = 0;
|
||||
bool success = false;
|
||||
|
||||
utilsGenerateHexStringFromData(tik_filename, sizeof(tik_filename), id->c, sizeof(id->c), false);
|
||||
strcat(tik_filename, ".tik");
|
||||
|
||||
/* Get ticket entry info. */
|
||||
if (!gamecardGetHashFileSystemEntryInfoByName(HashFileSystemPartitionType_Secure, tik_filename, &tik_offset, &tik_size))
|
||||
{
|
||||
LOG_MSG_ERROR("Error retrieving offset and size for \"%s\" entry in secure hash FS partition!", tik_filename);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Validate ticket size. */
|
||||
if (tik_size < SIGNED_TIK_MIN_SIZE || tik_size > SIGNED_TIK_MAX_SIZE)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid size for \"%s\"! (0x%lX).", tik_filename, tik_size);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Read ticket data. */
|
||||
if (!gamecardReadStorage(dst->data, tik_size, tik_offset))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to read \"%s\" data from the inserted gamecard!", tik_filename);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!tikGetTicketTypeAndSize(dst->data, tik_size, &(dst->type), &(dst->size)))
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to determine ticket type and size!");
|
||||
return false;
|
||||
}
|
||||
/* Get ticket type and size. */
|
||||
if (!(success = tikGetTicketTypeAndSize(dst->data, tik_size, &(dst->type), &(dst->size)))) LOG_MSG_ERROR("Unable to determine ticket type and size!");
|
||||
|
||||
return true;
|
||||
end:
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRightsId *id)
|
||||
|
@ -304,7 +330,7 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
|
|||
if (!(buf = malloc(buf_size)))
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to allocate 0x%lX bytes block for temporary read buffer!", buf_size);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Get titlekey type. */
|
||||
|
@ -322,30 +348,29 @@ static bool tikRetrieveTicketFromEsSaveDataByRightsId(Ticket *dst, const FsRight
|
|||
}
|
||||
|
||||
/* Get ticket entry offset from ticket_list.bin. */
|
||||
if (!tikGetTicketEntryOffsetFromTicketList(save_ctx, buf, buf_size, id, &ticket_offset, titlekey_type))
|
||||
if (!tikGetTicketEntryOffsetFromTicketList(save_ctx, buf, buf_size, id, titlekey_type, &ticket_offset))
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to find an entry with a matching Rights ID in \"%s\" from ES %s ticket system save!", TIK_LIST_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Get ticket entry from ticket.bin. */
|
||||
if (!tikRetrieveTicketEntryFromTicketBin(save_ctx, buf, buf_size, id, ticket_offset, titlekey_type))
|
||||
if (!tikRetrieveTicketEntryFromTicketBin(save_ctx, buf, buf_size, id, titlekey_type, ticket_offset))
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to find a matching %s ticket entry for the provided Rights ID!", g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Get ticket type and size. */
|
||||
if (!tikGetTicketTypeAndSize(buf, SIGNED_TIK_MAX_SIZE, &(dst->type), &(dst->size)))
|
||||
if (!(success = tikGetTicketTypeAndSize(buf, SIGNED_TIK_MAX_SIZE, &(dst->type), &(dst->size))))
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to determine ticket type and size!");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Copy ticket data. */
|
||||
memcpy(dst->data, buf, dst->size);
|
||||
|
||||
success = true;
|
||||
|
||||
end:
|
||||
if (save_ctx) save_close_savefile(save_ctx);
|
||||
|
||||
|
@ -354,33 +379,121 @@ end:
|
|||
return success;
|
||||
}
|
||||
|
||||
static bool tikGetEncryptedTitleKeyFromTicket(Ticket *tik)
|
||||
static bool tikFixTamperedCommonTicket(Ticket *tik)
|
||||
{
|
||||
TikCommonBlock *tik_common_block = NULL;
|
||||
|
||||
if (!tik || !(tik_common_block = tikGetCommonBlock(tik->data)))
|
||||
u32 sig_type = 0;
|
||||
u8 *signature = NULL;
|
||||
u64 signature_size = 0, hash_area_size = 0;
|
||||
|
||||
bool success = false;
|
||||
|
||||
if (!tik || tik->key_generation > NcaKeyGeneration_Max || !(tik_common_block = tikGetCommonBlockFromSignedTicketBlob(tik->data)))
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Get ticket signature and its properties, as well as the ticket hash area size. */
|
||||
sig_type = signatureGetTypeFromSignedBlob(tik->data, false);
|
||||
signature = signatureGetSigFromSignedBlob(tik->data);
|
||||
signature_size = signatureGetSigSizeByType(sig_type);
|
||||
hash_area_size = tikGetSignedTicketBlobHashAreaSize(tik->data);
|
||||
|
||||
/* Return right away if we're not dealing with a common ticket, if the signature type doesn't match RSA-2048 + SHA-256, or if the signature is valid. */
|
||||
if (tik_common_block->titlekey_type != TikTitleKeyType_Common || sig_type != SignatureType_Rsa2048Sha256 || \
|
||||
tikVerifyRsa2048Sha256Signature(tik_common_block, hash_area_size, signature))
|
||||
{
|
||||
success = true;
|
||||
goto end;
|
||||
}
|
||||
|
||||
LOG_MSG_DEBUG("Detected tampered common ticket!");
|
||||
|
||||
/* Nintendo didn't start putting the key generation value into the rights ID until HOS 3.0.1. */
|
||||
/* Old custom tools used to wipe the key generation field and/or save its value into a different offset. */
|
||||
/* We're gonna take care of that by setting the correct values where they need to go. */
|
||||
memset(signature, 0xFF, signature_size);
|
||||
|
||||
tik_common_block->titlekey_type = TikTitleKeyType_Common;
|
||||
tik_common_block->license_type = TikLicenseType_Permanent;
|
||||
tik_common_block->key_generation = tik->key_generation;
|
||||
tik_common_block->property_mask = TikPropertyMask_None;
|
||||
|
||||
tik_common_block->ticket_id = 0;
|
||||
tik_common_block->device_id = 0;
|
||||
tik_common_block->account_id = 0;
|
||||
|
||||
tik_common_block->sect_total_size = 0;
|
||||
tik_common_block->sect_hdr_offset = (u32)tik->size;
|
||||
tik_common_block->sect_hdr_count = 0;
|
||||
tik_common_block->sect_hdr_entry_size = 0;
|
||||
|
||||
/* Update return value. */
|
||||
success = true;
|
||||
|
||||
end:
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool tikVerifyRsa2048Sha256Signature(const TikCommonBlock *tik_common_block, u64 hash_area_size, const u8 *signature)
|
||||
{
|
||||
if (!tik_common_block || hash_area_size < sizeof(TikCommonBlock) || hash_area_size > (SIGNED_TIK_MAX_SIZE - sizeof(SignatureBlockRsa2048)) || !signature)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *cert_name = (strrchr(tik_common_block->issuer, '-') + 1);
|
||||
Certificate cert = {0};
|
||||
const u8 *modulus = NULL, *public_exponent = NULL;
|
||||
|
||||
/* Get certificate for the ticket signature issuer. */
|
||||
if (!certRetrieveCertificateByName(&cert, cert_name))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to retrieve certificate for \"%s\".", cert_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Get certificate modulus and public exponent. */
|
||||
modulus = certGetPublicKeyFromCertificate(&cert);
|
||||
public_exponent = certGetPublicExponentFromCertificate(&cert);
|
||||
|
||||
/* Validate the ticket signature. */
|
||||
return rsa2048VerifySha256BasedPkcs1v15Signature(tik_common_block, hash_area_size, signature, modulus, public_exponent, CERT_RSA_PUB_EXP_SIZE);
|
||||
}
|
||||
|
||||
static bool tikGetEncryptedTitleKey(Ticket *tik)
|
||||
{
|
||||
TikCommonBlock *tik_common_block = NULL;
|
||||
|
||||
if (!tik || !(tik_common_block = tikGetCommonBlockFromSignedTicketBlob(tik->data)))
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
|
||||
switch(tik_common_block->titlekey_type)
|
||||
{
|
||||
case TikTitleKeyType_Common:
|
||||
/* No console-specific crypto used. Copy encrypted titlekey right away. */
|
||||
memcpy(tik->enc_titlekey, tik_common_block->titlekey_block, 0x10);
|
||||
memcpy(tik->enc_titlekey, tik_common_block->titlekey_block, sizeof(tik->enc_titlekey));
|
||||
success = true;
|
||||
break;
|
||||
case TikTitleKeyType_Personalized:
|
||||
/* The titlekey block is encrypted using RSA-OAEP with a console-specific RSA key. */
|
||||
/* We have to perform a RSA-OAEP unwrap operation to get the encrypted titlekey. */
|
||||
if (!keysDecryptRsaOaepWrappedTitleKey(tik_common_block->titlekey_block, tik->enc_titlekey)) return false;
|
||||
success = keysDecryptRsaOaepWrappedTitleKey(tik_common_block->titlekey_block, tik->enc_titlekey);
|
||||
break;
|
||||
default:
|
||||
LOG_MSG_ERROR("Invalid titlekey type value! (0x%02X).", tik_common_block->titlekey_type);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool tikGetDecryptedTitleKey(void *dst, const void *src, u8 key_generation)
|
||||
|
@ -417,30 +530,27 @@ static bool tikGetTitleKeyTypeFromRightsId(const FsRightsId *id, u8 *out)
|
|||
FsRightsId *rights_ids = NULL;
|
||||
bool found = false;
|
||||
|
||||
for(u8 i = 0; i < 2; i++)
|
||||
for(u8 i = TikTitleKeyType_Common; i < TikTitleKeyType_Count; i++)
|
||||
{
|
||||
count = 0;
|
||||
rights_ids = NULL;
|
||||
|
||||
if (!tikRetrieveRightsIdsByTitleKeyType(&rights_ids, &count, i == 1))
|
||||
/* Get all rights IDs for the current titlekey type. */
|
||||
if (!tikRetrieveRightsIdsByTitleKeyType(&rights_ids, &count, i == TikTitleKeyType_Personalized))
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to retrieve %s rights IDs!", g_tikTitleKeyTypeStrings[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!count) continue;
|
||||
|
||||
/* Look for the provided rights ID. */
|
||||
for(u32 j = 0; j < count; j++)
|
||||
{
|
||||
if (!memcmp(rights_ids[j].c, id->c, 0x10))
|
||||
if (!memcmp(rights_ids[j].c, id->c, sizeof(id->c)))
|
||||
{
|
||||
*out = i; /* TikTitleKeyType_Common or TikTitleKeyType_Personalized. */
|
||||
*out = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(rights_ids);
|
||||
if (rights_ids) free(rights_ids);
|
||||
|
||||
if (found) break;
|
||||
}
|
||||
|
@ -459,6 +569,7 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count,
|
|||
Result rc = 0;
|
||||
u32 count = 0, ids_written = 0;
|
||||
FsRightsId *rights_ids = NULL;
|
||||
bool success = false;
|
||||
|
||||
#if LOG_LEVEL <= LOG_LEVEL_ERROR
|
||||
u8 str_idx = (personalized ? TikTitleKeyType_Personalized : TikTitleKeyType_Common);
|
||||
|
@ -467,43 +578,51 @@ static bool tikRetrieveRightsIdsByTitleKeyType(FsRightsId **out, u32 *out_count,
|
|||
*out = NULL;
|
||||
*out_count = 0;
|
||||
|
||||
/* Get ticket count for the provided titlekey type. */
|
||||
rc = (personalized ? esCountPersonalizedTicket((s32*)&count) : esCountCommonTicket((s32*)&count));
|
||||
if (R_FAILED(rc))
|
||||
{
|
||||
LOG_MSG_ERROR("esCount%c%sTicket failed! (0x%X).", toupper(g_tikTitleKeyTypeStrings[str_idx][0]), g_tikTitleKeyTypeStrings[str_idx] + 1, rc);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!count)
|
||||
{
|
||||
LOG_MSG_ERROR("No %s tickets available!", g_tikTitleKeyTypeStrings[str_idx]);
|
||||
return true;
|
||||
LOG_MSG_WARNING("No %s tickets available!", g_tikTitleKeyTypeStrings[str_idx]);
|
||||
success = true;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Allocate memory for our rights ID array. */
|
||||
rights_ids = calloc(count, sizeof(FsRightsId));
|
||||
if (!rights_ids)
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to allocate memory for %s rights IDs!", g_tikTitleKeyTypeStrings[str_idx]);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Get rights IDs from all tickets that match the provided titlekey type. */
|
||||
rc = (personalized ? esListPersonalizedTicket((s32*)&ids_written, rights_ids, (s32)count) : esListCommonTicket((s32*)&ids_written, rights_ids, (s32)count));
|
||||
if (R_FAILED(rc) || !ids_written)
|
||||
success = (R_SUCCEEDED(rc) && ids_written);
|
||||
if (!success)
|
||||
{
|
||||
LOG_MSG_ERROR("esList%c%sTicket failed! (0x%X). Wrote %u entries, expected %u entries.", toupper(g_tikTitleKeyTypeStrings[str_idx][0]), g_tikTitleKeyTypeStrings[str_idx] + 1, rc, ids_written, count);
|
||||
free(rights_ids);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Update output values. */
|
||||
*out = rights_ids;
|
||||
*out_count = ids_written;
|
||||
|
||||
return true;
|
||||
end:
|
||||
if (!success && rights_ids) free(rights_ids);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool tikGetTicketEntryOffsetFromTicketList(save_ctx_t *save_ctx, u8 *buf, u64 buf_size, const FsRightsId *id, u64 *out_offset, u8 titlekey_type)
|
||||
static bool tikGetTicketEntryOffsetFromTicketList(save_ctx_t *save_ctx, u8 *buf, u64 buf_size, const FsRightsId *id, u8 titlekey_type, u64 *out_offset)
|
||||
{
|
||||
if (!save_ctx || !buf || !buf_size || (buf_size % sizeof(TikListEntry)) != 0 || !id || !out_offset)
|
||||
if (!save_ctx || !buf || !buf_size || (buf_size % sizeof(TikListEntry)) != 0 || !id || titlekey_type >= TikTitleKeyType_Count || !out_offset)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
|
@ -512,7 +631,7 @@ static bool tikGetTicketEntryOffsetFromTicketList(save_ctx_t *save_ctx, u8 *buf,
|
|||
allocation_table_storage_ctx_t fat_storage = {0};
|
||||
u64 ticket_list_bin_size = 0, br = 0, total_br = 0;
|
||||
|
||||
u8 last_rights_id[0x10];
|
||||
u8 last_rights_id[0x10] = {0};
|
||||
memset(last_rights_id, 0xFF, sizeof(last_rights_id));
|
||||
|
||||
bool last_entry_found = false, success = false;
|
||||
|
@ -521,27 +640,30 @@ static bool tikGetTicketEntryOffsetFromTicketList(save_ctx_t *save_ctx, u8 *buf,
|
|||
if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_LIST_STORAGE_PATH, &fat_storage, &ticket_list_bin_size))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to locate \"%s\" in ES %s ticket system save!", TIK_LIST_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Check ticket_list.bin size. */
|
||||
/* Validate ticket_list.bin size. */
|
||||
if (ticket_list_bin_size < sizeof(TikListEntry) || (ticket_list_bin_size % sizeof(TikListEntry)) != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid size for \"%s\" in ES %s ticket system save! (0x%lX).", TIK_LIST_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type], ticket_list_bin_size);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Look for an entry matching our rights ID in ticket_list.bin. */
|
||||
while(total_br < ticket_list_bin_size)
|
||||
{
|
||||
/* Update chunk size, if needed. */
|
||||
if (buf_size > (ticket_list_bin_size - total_br)) buf_size = (ticket_list_bin_size - total_br);
|
||||
|
||||
/* Read current chunk. */
|
||||
if ((br = save_allocation_table_storage_read(&fat_storage, buf, total_br, buf_size)) != buf_size)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to read 0x%lX bytes chunk at offset 0x%lX from \"%s\" in ES %s ticket system save!", buf_size, total_br, TIK_LIST_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Process individual ticket list entries. */
|
||||
for(u64 i = 0; i < buf_size; i += sizeof(TikListEntry))
|
||||
{
|
||||
if ((buf_size - i) < sizeof(TikListEntry)) break;
|
||||
|
@ -571,12 +693,13 @@ static bool tikGetTicketEntryOffsetFromTicketList(save_ctx_t *save_ctx, u8 *buf,
|
|||
if (last_entry_found || success) break;
|
||||
}
|
||||
|
||||
end:
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool tikRetrieveTicketEntryFromTicketBin(save_ctx_t *save_ctx, u8 *buf, u64 buf_size, const FsRightsId *id, u64 ticket_offset, u8 titlekey_type)
|
||||
static bool tikRetrieveTicketEntryFromTicketBin(save_ctx_t *save_ctx, u8 *buf, u64 buf_size, const FsRightsId *id, u8 titlekey_type, u64 ticket_offset)
|
||||
{
|
||||
if (!save_ctx || !buf || buf_size < SIGNED_TIK_MAX_SIZE || !id || (ticket_offset % SIGNED_TIK_MAX_SIZE) != 0)
|
||||
if (!save_ctx || !buf || buf_size < SIGNED_TIK_MAX_SIZE || !id || titlekey_type >= TikTitleKeyType_Count || (ticket_offset % SIGNED_TIK_MAX_SIZE) != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
|
@ -587,120 +710,142 @@ static bool tikRetrieveTicketEntryFromTicketBin(save_ctx_t *save_ctx, u8 *buf, u
|
|||
|
||||
TikCommonBlock *tik_common_block = NULL;
|
||||
|
||||
Aes128CtrContext ctr_ctx = {0};
|
||||
u8 null_ctr[AES_128_KEY_SIZE] = {0}, ctr[AES_128_KEY_SIZE] = {0}, dec_tik[SIGNED_TIK_MAX_SIZE] = {0};
|
||||
|
||||
bool is_volatile = false, success = false;
|
||||
|
||||
/* Get FAT storage info for the ticket.bin stored within the opened system savefile. */
|
||||
if (!save_get_fat_storage_from_file_entry_by_path(save_ctx, TIK_DB_STORAGE_PATH, &fat_storage, &ticket_bin_size))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to locate \"%s\" in ES %s ticket system save!", TIK_DB_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Check ticket.bin size. */
|
||||
/* Validate ticket.bin size. */
|
||||
if (ticket_bin_size < SIGNED_TIK_MIN_SIZE || (ticket_bin_size % SIGNED_TIK_MAX_SIZE) != 0 || ticket_bin_size < (ticket_offset + SIGNED_TIK_MAX_SIZE))
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid size for \"%s\" in ES %s ticket system save! (0x%lX).", TIK_DB_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type], ticket_bin_size);
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Read ticket data. */
|
||||
if ((br = save_allocation_table_storage_read(&fat_storage, buf, ticket_offset, SIGNED_TIK_MAX_SIZE)) != SIGNED_TIK_MAX_SIZE)
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to read 0x%X-byte long ticket at offset 0x%lX from \"%s\" in ES %s ticket system save!", SIGNED_TIK_MAX_SIZE, ticket_offset, TIK_DB_STORAGE_PATH, \
|
||||
g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||
return false;
|
||||
g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Get ticket common block. */
|
||||
tik_common_block = tikGetCommonBlockFromSignedTicketBlob(buf);
|
||||
|
||||
/* Check if we're dealing with a volatile (encrypted) ticket. */
|
||||
if (!(tik_common_block = tikGetCommonBlock(buf)) || strncmp(tik_common_block->issuer, "Root-", 5) != 0)
|
||||
is_volatile = (!tik_common_block || strncmp(tik_common_block->issuer, "Root-", 5) != 0);
|
||||
if (is_volatile)
|
||||
{
|
||||
tik_common_block = NULL;
|
||||
is_volatile = true;
|
||||
|
||||
/* Don't proceed if HOS version isn't at least 9.0.0. */
|
||||
if (!hosversionAtLeast(9, 0, 0))
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to retrieve ES key entry for volatile tickets under HOS versions below 9.0.0!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Retrieve ES program memory. */
|
||||
if (!memRetrieveFullProgramMemory(&g_esMemoryLocation))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to retrieve ES program memory!");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Retrieve the CTR key/IV from ES program memory in order to decrypt this ticket. */
|
||||
for(u64 i = 0; i < g_esMemoryLocation.data_size; i++)
|
||||
{
|
||||
if ((g_esMemoryLocation.data_size - i) < (sizeof(TikEsCtrKeyEntry9x) * 2)) break;
|
||||
|
||||
/* Check if the key indexes are valid. idx2 should always be an odd number equal to idx + 1. */
|
||||
TikEsCtrKeyPattern9x *pattern = (TikEsCtrKeyPattern9x*)(g_esMemoryLocation.data + i);
|
||||
if (pattern->idx2 != (pattern->idx1 + 1) || !(pattern->idx2 & 1)) continue;
|
||||
|
||||
/* Check if the key is not null and if the CTR is. */
|
||||
TikEsCtrKeyEntry9x *key_entry = (TikEsCtrKeyEntry9x*)pattern;
|
||||
if (!memcmp(key_entry->key, null_ctr, sizeof(null_ctr)) || memcmp(key_entry->ctr, null_ctr, sizeof(null_ctr)) != 0) continue;
|
||||
|
||||
/* Check if we can decrypt the current ticket with this data. */
|
||||
memset(&ctr_ctx, 0, sizeof(Aes128CtrContext));
|
||||
aes128CtrInitializePartialCtr(ctr, key_entry->ctr, ticket_offset);
|
||||
aes128CtrContextCreate(&ctr_ctx, key_entry->key, ctr);
|
||||
aes128CtrCrypt(&ctr_ctx, dec_tik, buf, SIGNED_TIK_MAX_SIZE);
|
||||
|
||||
/* Check if we successfully decrypted this ticket. */
|
||||
if ((tik_common_block = tikGetCommonBlock(dec_tik)) != NULL && !strncmp(tik_common_block->issuer, "Root-", 5))
|
||||
{
|
||||
memcpy(buf, dec_tik, SIGNED_TIK_MAX_SIZE);
|
||||
tik_common_block = tikGetCommonBlock(buf);
|
||||
break;
|
||||
}
|
||||
|
||||
tik_common_block = NULL;
|
||||
}
|
||||
|
||||
/* Check if we were able to decrypt the ticket. */
|
||||
if (!tik_common_block)
|
||||
/* Attempt to decrypt the ticket. */
|
||||
if (!tikDecryptVolatileTicket(buf, ticket_offset))
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to decrypt volatile ticket at offset 0x%lX in \"%s\" from ES %s ticket system save!", ticket_offset, TIK_DB_STORAGE_PATH, g_tikTitleKeyTypeStrings[titlekey_type]);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Get ticket common block. */
|
||||
tik_common_block = tikGetCommonBlockFromSignedTicketBlob(buf);
|
||||
}
|
||||
|
||||
/* Check if the rights ID from the ticket common block matches the one we're looking for. */
|
||||
if (!(success = (memcmp(tik_common_block->rights_id.c, id->c, 0x10) == 0))) LOG_MSG_ERROR("Retrieved ticket doesn't hold a matching Rights ID!");
|
||||
if (!(success = (memcmp(tik_common_block->rights_id.c, id->c, sizeof(id->c)) == 0))) LOG_MSG_ERROR("Retrieved ticket doesn't hold a matching Rights ID!");
|
||||
|
||||
end:
|
||||
if (is_volatile) memFreeMemoryLocation(&g_esMemoryLocation);
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool tikDecryptVolatileTicket(u8 *buf, u64 ticket_offset)
|
||||
{
|
||||
if (!buf || (ticket_offset % SIGNED_TIK_MAX_SIZE) != 0)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
Aes128CtrContext ctr_ctx = {0};
|
||||
u8 null_ctr[AES_128_KEY_SIZE] = {0}, ctr[AES_128_KEY_SIZE] = {0}, dec_tik[SIGNED_TIK_MAX_SIZE] = {0};
|
||||
TikCommonBlock *tik_common_block = NULL;
|
||||
bool success = false;
|
||||
|
||||
/* Don't proceed if HOS version isn't at least 9.0.0. */
|
||||
if (!hosversionAtLeast(9, 0, 0))
|
||||
{
|
||||
LOG_MSG_ERROR("Unable to retrieve ES key entry for volatile tickets under HOS versions below 9.0.0!");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Retrieve ES program memory. */
|
||||
if (!memRetrieveFullProgramMemory(&g_esMemoryLocation))
|
||||
{
|
||||
LOG_MSG_ERROR("Failed to retrieve ES program memory!");
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Retrieve the CTR key/IV from ES program memory in order to decrypt this ticket. */
|
||||
for(u64 i = 0; i < g_esMemoryLocation.data_size; i++)
|
||||
{
|
||||
if ((g_esMemoryLocation.data_size - i) < (sizeof(TikEsCtrKeyEntry9x) * 2)) break;
|
||||
|
||||
/* Check if the key indexes are valid. idx2 should always be an odd number equal to idx + 1. */
|
||||
TikEsCtrKeyPattern9x *pattern = (TikEsCtrKeyPattern9x*)(g_esMemoryLocation.data + i);
|
||||
if (pattern->idx2 != (pattern->idx1 + 1) || !(pattern->idx2 & 1)) continue;
|
||||
|
||||
/* Check if the key is not null and if the CTR is. */
|
||||
TikEsCtrKeyEntry9x *key_entry = (TikEsCtrKeyEntry9x*)pattern;
|
||||
if (!memcmp(key_entry->key, null_ctr, sizeof(null_ctr)) || memcmp(key_entry->ctr, null_ctr, sizeof(null_ctr)) != 0) continue;
|
||||
|
||||
/* Check if we can decrypt the current ticket with this data. */
|
||||
memset(&ctr_ctx, 0, sizeof(Aes128CtrContext));
|
||||
aes128CtrInitializePartialCtr(ctr, key_entry->ctr, ticket_offset);
|
||||
aes128CtrContextCreate(&ctr_ctx, key_entry->key, ctr);
|
||||
aes128CtrCrypt(&ctr_ctx, dec_tik, buf, SIGNED_TIK_MAX_SIZE);
|
||||
|
||||
/* Check if we successfully decrypted this ticket. */
|
||||
if ((tik_common_block = tikGetCommonBlockFromSignedTicketBlob(dec_tik)) != NULL && !strncmp(tik_common_block->issuer, "Root-", 5))
|
||||
{
|
||||
memcpy(buf, dec_tik, SIGNED_TIK_MAX_SIZE);
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) LOG_MSG_ERROR("Unable to find ES memory key entry!");
|
||||
|
||||
end:
|
||||
memFreeMemoryLocation(&g_esMemoryLocation);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static bool tikGetTicketTypeAndSize(void *data, u64 data_size, u8 *out_type, u64 *out_size)
|
||||
{
|
||||
u32 sig_type = 0;
|
||||
u64 signed_ticket_size = 0;
|
||||
u8 type = TikType_None;
|
||||
|
||||
if (!data || data_size < SIGNED_TIK_MIN_SIZE || data_size > SIGNED_TIK_MAX_SIZE || !out_type || !out_size)
|
||||
{
|
||||
LOG_MSG_ERROR("Invalid parameters!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(signed_ticket_size = tikGetSignedTicketSize(data)) || signed_ticket_size > data_size)
|
||||
u32 sig_type = 0;
|
||||
u64 signed_ticket_size = 0;
|
||||
u8 type = TikType_None;
|
||||
bool success = false;
|
||||
|
||||
/* Get signature type and signed ticket size. */
|
||||
sig_type = signatureGetTypeFromSignedBlob(data, false);
|
||||
signed_ticket_size = tikGetSignedTicketBlobSize(data);
|
||||
|
||||
if (!signatureIsValidType(sig_type) || signed_ticket_size < SIGNED_TIK_MIN_SIZE || signed_ticket_size > data_size)
|
||||
{
|
||||
LOG_MSG_ERROR("Input buffer doesn't hold a valid signed ticket!");
|
||||
return false;
|
||||
goto end;
|
||||
}
|
||||
|
||||
sig_type = signatureGetSigType(data, false);
|
||||
|
||||
/* Determine ticket type. */
|
||||
switch(sig_type)
|
||||
{
|
||||
case SignatureType_Rsa4096Sha1:
|
||||
|
@ -722,8 +867,14 @@ static bool tikGetTicketTypeAndSize(void *data, u64 data_size, u8 *out_type, u64
|
|||
break;
|
||||
}
|
||||
|
||||
*out_type = type;
|
||||
*out_size = signed_ticket_size;
|
||||
/* Update output. */
|
||||
success = (type > TikType_None && type < TikType_Count);
|
||||
if (success)
|
||||
{
|
||||
*out_type = type;
|
||||
*out_size = signed_ticket_size;
|
||||
}
|
||||
|
||||
return true;
|
||||
end:
|
||||
return success;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue