add basic support for title installing
This commit is contained in:
57
sphaira/include/yati/nx/crypto.hpp
Normal file
57
sphaira/include/yati/nx/crypto.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::crypto {
|
||||
|
||||
struct Aes128 {
|
||||
Aes128(const void *key, bool is_encryptor) {
|
||||
m_is_encryptor = is_encryptor;
|
||||
aes128ContextCreate(&m_ctx, key, is_encryptor);
|
||||
}
|
||||
|
||||
void Run(void *dst, const void *src) {
|
||||
if (m_is_encryptor) {
|
||||
aes128EncryptBlock(&m_ctx, dst, src);
|
||||
} else {
|
||||
aes128DecryptBlock(&m_ctx, dst, src);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Aes128Context m_ctx;
|
||||
bool m_is_encryptor;
|
||||
};
|
||||
|
||||
struct Aes128Xts {
|
||||
Aes128Xts(const u8 *key, bool is_encryptor) : Aes128Xts{key, key + 0x10, is_encryptor} { }
|
||||
Aes128Xts(const void *key0, const void *key1, bool is_encryptor) {
|
||||
m_is_encryptor = is_encryptor;
|
||||
aes128XtsContextCreate(&m_ctx, key0, key1, is_encryptor);
|
||||
}
|
||||
|
||||
void Run(void *dst, const void *src, u64 sector, u64 sector_size, u64 data_size) {
|
||||
for (u64 pos = 0; pos < data_size; pos += sector_size) {
|
||||
aes128XtsContextResetSector(&m_ctx, sector++, true);
|
||||
if (m_is_encryptor) {
|
||||
aes128XtsEncrypt(&m_ctx, static_cast<u8*>(dst) + pos, static_cast<const u8*>(src) + pos, sector_size);
|
||||
} else {
|
||||
aes128XtsDecrypt(&m_ctx, static_cast<u8*>(dst) + pos, static_cast<const u8*>(src) + pos, sector_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Aes128XtsContext m_ctx;
|
||||
bool m_is_encryptor;
|
||||
};
|
||||
|
||||
static inline void cryptoAes128(const void *in, void *out, const void* key, bool is_encryptor) {
|
||||
Aes128(key, is_encryptor).Run(out, in);
|
||||
}
|
||||
|
||||
static inline void cryptoAes128Xts(const void* in, void* out, const u8* key, u64 sector, u64 sector_size, u64 data_size, bool is_encryptor) {
|
||||
Aes128Xts(key, is_encryptor).Run(out, in, sector, sector_size, data_size);
|
||||
}
|
||||
|
||||
} // namespace sphaira::crypto
|
||||
83
sphaira/include/yati/nx/es.hpp
Normal file
83
sphaira/include/yati/nx/es.hpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include <span>
|
||||
#include "ncm.hpp"
|
||||
#include "keys.hpp"
|
||||
|
||||
namespace sphaira::es {
|
||||
|
||||
enum { TicketModule = 522 };
|
||||
|
||||
enum : Result {
|
||||
// found ticket has missmatching rights_id from it's name.
|
||||
Result_InvalidTicketBadRightsId = MAKERESULT(TicketModule, 71),
|
||||
Result_InvalidTicketVersion = MAKERESULT(TicketModule, 72),
|
||||
Result_InvalidTicketKeyType = MAKERESULT(TicketModule, 73),
|
||||
Result_InvalidTicketKeyRevision = MAKERESULT(TicketModule, 74),
|
||||
};
|
||||
|
||||
enum TicketSigantureType {
|
||||
TicketSigantureType_RSA_4096_SHA1 = 0x010000,
|
||||
TicketSigantureType_RSA_2048_SHA1 = 0x010001,
|
||||
TicketSigantureType_ECDSA_SHA1 = 0x010002,
|
||||
TicketSigantureType_RSA_4096_SHA256 = 0x010003,
|
||||
TicketSigantureType_RSA_2048_SHA256 = 0x010004,
|
||||
TicketSigantureType_ECDSA_SHA256 = 0x010005,
|
||||
TicketSigantureType_HMAC_SHA1_160 = 0x010006,
|
||||
};
|
||||
|
||||
enum TicketTitleKeyType {
|
||||
TicketTitleKeyType_Common = 0,
|
||||
TicketTitleKeyType_Personalized = 1,
|
||||
};
|
||||
|
||||
enum TicketPropertiesBitfield {
|
||||
TicketPropertiesBitfield_None = 0,
|
||||
// temporary ticket, removed on restart
|
||||
TicketPropertiesBitfield_Temporary = 1 << 4,
|
||||
};
|
||||
|
||||
struct TicketData {
|
||||
u8 issuer[0x40];
|
||||
u8 title_key_block[0x100];
|
||||
u8 ticket_version1;
|
||||
u8 title_key_type;
|
||||
u16 ticket_version2;
|
||||
u8 license_type;
|
||||
u8 master_key_revision;
|
||||
u16 properties_bitfield;
|
||||
u8 _0x148[0x8];
|
||||
u64 ticket_id;
|
||||
u64 device_id;
|
||||
FsRightsId rights_id;
|
||||
u32 account_id;
|
||||
u8 _0x174[0xC];
|
||||
u8 _0x180[0x140];
|
||||
};
|
||||
static_assert(sizeof(TicketData) == 0x2C0);
|
||||
|
||||
struct EticketRsaDeviceKey {
|
||||
u8 ctr[AES_128_KEY_SIZE];
|
||||
u8 private_exponent[0x100];
|
||||
u8 modulus[0x100];
|
||||
u32 public_exponent; ///< Stored using big endian byte order. Must match ETICKET_RSA_DEVICE_KEY_PUBLIC_EXPONENT.
|
||||
u8 padding[0x14];
|
||||
u64 device_id;
|
||||
u8 ghash[0x10];
|
||||
};
|
||||
static_assert(sizeof(EticketRsaDeviceKey) == 0x240);
|
||||
|
||||
// es functions.
|
||||
Result ImportTicket(Service* srv, const void* tik_buf, u64 tik_size, const void* cert_buf, u64 cert_size);
|
||||
|
||||
// ticket functions.
|
||||
Result GetTicketDataOffset(std::span<const u8> ticket, u64& out);
|
||||
Result GetTicketData(std::span<const u8> ticket, es::TicketData* out);
|
||||
Result SetTicketData(std::span<u8> ticket, const es::TicketData* in);
|
||||
|
||||
Result GetTitleKey(keys::KeyEntry& out, const TicketData& data, const keys::Keys& keys);
|
||||
Result DecryptTitleKey(keys::KeyEntry& out, u8 key_gen, const keys::Keys& keys);
|
||||
Result PatchTicket(std::span<u8> ticket, const keys::Keys& keys, bool convert_personalised);
|
||||
|
||||
} // namespace sphaira::es
|
||||
70
sphaira/include/yati/nx/keys.hpp
Normal file
70
sphaira/include/yati/nx/keys.hpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include "defines.hpp"
|
||||
|
||||
namespace sphaira::keys {
|
||||
|
||||
struct KeyEntry {
|
||||
u8 key[AES_128_KEY_SIZE]{};
|
||||
|
||||
auto IsValid() const -> bool {
|
||||
const KeyEntry empty{};
|
||||
return std::memcmp(key, &empty, sizeof(key));
|
||||
}
|
||||
};
|
||||
|
||||
using KeySection = std::array<KeyEntry, 0x20>;
|
||||
struct Keys {
|
||||
u8 header_key[0x20]{};
|
||||
// the below are only found if read_from_file=true
|
||||
KeySection key_area_key[0x3]{}; // index
|
||||
KeySection titlekek{};
|
||||
KeySection master_key{};
|
||||
KeyEntry eticket_rsa_kek{};
|
||||
SetCalRsa2048DeviceKey eticket_device_key{};
|
||||
|
||||
static auto FixKey(u8 key) -> u8 {
|
||||
if (key) {
|
||||
return key - 1;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
auto HasNcaKeyArea(u8 key, u8 index) const -> bool {
|
||||
return key_area_key[index][FixKey(key)].IsValid();
|
||||
}
|
||||
|
||||
auto HasTitleKek(u8 key) const -> bool {
|
||||
return titlekek[FixKey(key)].IsValid();
|
||||
}
|
||||
|
||||
auto HasMasterKey(u8 key) const -> bool {
|
||||
return master_key[FixKey(key)].IsValid();
|
||||
}
|
||||
|
||||
auto GetNcaKeyArea(KeyEntry* out, u8 key, u8 index) const -> Result {
|
||||
R_UNLESS(HasNcaKeyArea(key, index), 0x1);
|
||||
*out = key_area_key[index][FixKey(key)];
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
auto GetTitleKek(KeyEntry* out, u8 key) const -> Result {
|
||||
R_UNLESS(HasTitleKek(key), 0x1);
|
||||
*out = titlekek[FixKey(key)];
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
auto GetMasterKey(KeyEntry* out, u8 key) const -> Result {
|
||||
R_UNLESS(HasMasterKey(key), 0x1);
|
||||
*out = master_key[FixKey(key)];
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
void parse_hex_key(void* key, const char* hex);
|
||||
Result parse_keys(Keys& out, bool read_from_file);
|
||||
|
||||
} // namespace sphaira::keys
|
||||
218
sphaira/include/yati/nx/nca.hpp
Normal file
218
sphaira/include/yati/nx/nca.hpp
Normal file
@@ -0,0 +1,218 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include "keys.hpp"
|
||||
|
||||
namespace sphaira::nca {
|
||||
|
||||
#define NCA0_MAGIC 0x3041434E
|
||||
#define NCA2_MAGIC 0x3241434E
|
||||
#define NCA3_MAGIC 0x3341434E
|
||||
|
||||
#define NCA_SECTOR_SIZE 0x200
|
||||
#define NCA_XTS_SECTION_SIZE 0xC00
|
||||
#define NCA_SECTION_TOTAL 0x4
|
||||
#define NCA_MEDIA_REAL(x)((x * 0x200))
|
||||
|
||||
#define NCA_PROGRAM_LOGO_OFFSET 0x8000
|
||||
#define NCA_META_CNMT_OFFSET 0xC20
|
||||
|
||||
enum KeyGenerationOld {
|
||||
KeyGenerationOld_100 = 0x0,
|
||||
KeyGenerationOld_Unused = 0x1,
|
||||
KeyGenerationOld_300 = 0x2,
|
||||
};
|
||||
|
||||
enum KeyGeneration {
|
||||
KeyGeneration_301 = 0x3,
|
||||
KeyGeneration_400 = 0x4,
|
||||
KeyGeneration_500 = 0x5,
|
||||
KeyGeneration_600 = 0x6,
|
||||
KeyGeneration_620 = 0x7,
|
||||
KeyGeneration_700 = 0x8,
|
||||
KeyGeneration_810 = 0x9,
|
||||
KeyGeneration_900 = 0x0A,
|
||||
KeyGeneration_910 = 0x0B,
|
||||
KeyGeneration_1210 = 0x0C,
|
||||
KeyGeneration_1300 = 0x0D,
|
||||
KeyGeneration_1400 = 0x0E,
|
||||
KeyGeneration_1500 = 0x0F,
|
||||
KeyGeneration_1600 = 0x10,
|
||||
KeyGeneration_1700 = 0x11,
|
||||
KeyGeneration_1800 = 0x12,
|
||||
KeyGeneration_1900 = 0x13,
|
||||
KeyGeneration_Invalid = 0xFF,
|
||||
};
|
||||
|
||||
enum KeyAreaEncryptionKeyIndex {
|
||||
KeyAreaEncryptionKeyIndex_Application = 0x0,
|
||||
KeyAreaEncryptionKeyIndex_Ocean = 0x1,
|
||||
KeyAreaEncryptionKeyIndex_System = 0x2
|
||||
};
|
||||
|
||||
enum DistributionType {
|
||||
DistributionType_System = 0x0,
|
||||
DistributionType_GameCard = 0x1
|
||||
};
|
||||
|
||||
enum ContentType {
|
||||
ContentType_Program = 0x0,
|
||||
ContentType_Meta = 0x1,
|
||||
ContentType_Control = 0x2,
|
||||
ContentType_Manual = 0x3,
|
||||
ContentType_Data = 0x4,
|
||||
ContentType_PublicData = 0x5,
|
||||
};
|
||||
|
||||
enum FileSystemType {
|
||||
FileSystemType_RomFS = 0x0,
|
||||
FileSystemType_PFS0 = 0x1
|
||||
};
|
||||
|
||||
enum HashType {
|
||||
HashType_Auto = 0x0,
|
||||
HashType_HierarchicalSha256 = 0x2,
|
||||
HashType_HierarchicalIntegrity = 0x3
|
||||
};
|
||||
|
||||
enum EncryptionType {
|
||||
EncryptionType_Auto = 0x0,
|
||||
EncryptionType_None = 0x1,
|
||||
EncryptionType_AesXts = 0x2,
|
||||
EncryptionType_AesCtr = 0x3,
|
||||
EncryptionType_AesCtrEx = 0x4,
|
||||
EncryptionType_AesCtrSkipLayerHash = 0x5, // [14.0.0+]
|
||||
EncryptionType_AesCtrExSkipLayerHash = 0x6, // [14.0.0+]
|
||||
};
|
||||
|
||||
struct SectionTableEntry {
|
||||
u32 media_start_offset; // divided by 0x200.
|
||||
u32 media_end_offset; // divided by 0x200.
|
||||
u8 _0x8[0x4]; // unknown.
|
||||
u8 _0xC[0x4]; // unknown.
|
||||
};
|
||||
|
||||
struct LayerRegion {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
struct HierarchicalSha256Data {
|
||||
u8 master_hash[0x20];
|
||||
u32 block_size;
|
||||
u32 layer_count;
|
||||
LayerRegion hash_layer;
|
||||
LayerRegion pfs0_layer;
|
||||
LayerRegion unused_layers[3];
|
||||
u8 _0x78[0x80];
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct HierarchicalIntegrityVerificationLevelInformation {
|
||||
u64 logical_offset;
|
||||
u64 hash_data_size;
|
||||
u32 block_size; // log2
|
||||
u32 _0x14; // reserved
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct InfoLevelHash {
|
||||
u32 max_layers;
|
||||
HierarchicalIntegrityVerificationLevelInformation levels[6];
|
||||
u8 signature_salt[0x20];
|
||||
};
|
||||
|
||||
struct IntegrityMetaInfo {
|
||||
u32 magic; // IVFC
|
||||
u32 version;
|
||||
u32 master_hash_size;
|
||||
InfoLevelHash info_level_hash;
|
||||
u8 master_hash[0x20];
|
||||
u8 _0xE0[0x18];
|
||||
};
|
||||
|
||||
static_assert(sizeof(HierarchicalSha256Data) == 0xF8);
|
||||
static_assert(sizeof(IntegrityMetaInfo) == 0xF8);
|
||||
static_assert(sizeof(HierarchicalSha256Data) == sizeof(IntegrityMetaInfo));
|
||||
|
||||
struct FsHeader {
|
||||
u16 version; // always 2.
|
||||
u8 fs_type; // see FileSystemType.
|
||||
u8 hash_type; // see HashType.
|
||||
u8 encryption_type; // see EncryptionType.
|
||||
u8 metadata_hash_type;
|
||||
u8 _0x6[0x2]; // empty.
|
||||
|
||||
union {
|
||||
HierarchicalSha256Data hierarchical_sha256_data;
|
||||
IntegrityMetaInfo integrity_meta_info; // used for romfs
|
||||
} hash_data;
|
||||
|
||||
u8 patch_info[0x40];
|
||||
u64 section_ctr;
|
||||
u8 spares_info[0x30];
|
||||
u8 compression_info[0x28];
|
||||
u8 meta_data_hash_data_info[0x30];
|
||||
u8 reserved[0x30];
|
||||
};
|
||||
static_assert(sizeof(FsHeader) == 0x200);
|
||||
static_assert(sizeof(FsHeader::hash_data) == 0xF8);
|
||||
|
||||
struct SectionHeaderHash {
|
||||
u8 sha256[0x20];
|
||||
};
|
||||
|
||||
struct KeyArea {
|
||||
u8 area[0x10];
|
||||
};
|
||||
|
||||
struct Header {
|
||||
u8 rsa_fixed_key[0x100];
|
||||
u8 rsa_npdm[0x100]; // key from npdm.
|
||||
u32 magic;
|
||||
u8 distribution_type; // see DistributionType.
|
||||
u8 content_type; // see ContentType.
|
||||
u8 old_key_gen; // see KeyGenerationOld.
|
||||
u8 kaek_index; // see KeyAreaEncryptionKeyIndex.
|
||||
u64 size;
|
||||
u64 title_id;
|
||||
u32 context_id;
|
||||
u32 sdk_version;
|
||||
u8 key_gen; // see KeyGeneration.
|
||||
u8 sig_key_gen;
|
||||
u8 _0x222[0xE]; // empty.
|
||||
FsRightsId rights_id;
|
||||
|
||||
SectionTableEntry fs_table[NCA_SECTION_TOTAL];
|
||||
SectionHeaderHash fs_header_hash[NCA_SECTION_TOTAL];
|
||||
KeyArea key_area[NCA_SECTION_TOTAL];
|
||||
|
||||
u8 _0x340[0xC0]; // empty.
|
||||
|
||||
FsHeader fs_header[NCA_SECTION_TOTAL];
|
||||
|
||||
auto GetKeyGeneration() const -> u8 {
|
||||
if (old_key_gen < key_gen) {
|
||||
return key_gen;
|
||||
} else {
|
||||
return old_key_gen;
|
||||
}
|
||||
}
|
||||
|
||||
void SetKeyGeneration(u8 key_generation) {
|
||||
if (key_generation <= 0x2) {
|
||||
old_key_gen = key_generation;
|
||||
key_gen = 0;
|
||||
} else {
|
||||
old_key_gen = 0x2;
|
||||
key_gen = key_generation;
|
||||
}
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(Header) == 0xC00);
|
||||
|
||||
Result DecryptKeak(const keys::Keys& keys, Header& header);
|
||||
Result EncryptKeak(const keys::Keys& keys, Header& header, u8 key_generation);
|
||||
Result VerifyFixedKey(const Header& header);
|
||||
|
||||
} // namespace sphaira::nca
|
||||
39
sphaira/include/yati/nx/ncm.hpp
Normal file
39
sphaira/include/yati/nx/ncm.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::ncm {
|
||||
|
||||
struct PackagedContentMeta {
|
||||
u64 title_id;
|
||||
u32 title_version;
|
||||
u8 meta_type; // NcmContentMetaType
|
||||
u8 content_meta_platform; // [17.0.0+]
|
||||
NcmContentMetaHeader meta_header;
|
||||
u8 install_type; // NcmContentInstallType
|
||||
u8 _0x17;
|
||||
u32 required_sys_version;
|
||||
u8 _0x1C[0x4];
|
||||
};
|
||||
static_assert(sizeof(PackagedContentMeta) == 0x20);
|
||||
|
||||
struct ContentStorageRecord {
|
||||
NcmContentMetaKey key;
|
||||
u8 storage_id;
|
||||
u8 padding[0x7];
|
||||
};
|
||||
|
||||
union ExtendedHeader {
|
||||
NcmApplicationMetaExtendedHeader application;
|
||||
NcmPatchMetaExtendedHeader patch;
|
||||
NcmAddOnContentMetaExtendedHeader addon;
|
||||
NcmLegacyAddOnContentMetaExtendedHeader addon_legacy;
|
||||
NcmDataPatchMetaExtendedHeader data_patch;
|
||||
};
|
||||
|
||||
auto GetAppId(const NcmContentMetaKey& key) -> u64;
|
||||
|
||||
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id);
|
||||
Result Register(NcmContentStorage* cs, const NcmContentId *content_id, const NcmPlaceHolderId *placeholder_id);
|
||||
|
||||
} // namespace sphaira::ncm
|
||||
54
sphaira/include/yati/nx/ncz.hpp
Normal file
54
sphaira/include/yati/nx/ncz.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::ncz {
|
||||
|
||||
#define NCZ_SECTION_MAGIC 0x4E544345535A434EUL
|
||||
// todo: byteswap this
|
||||
#define NCZ_BLOCK_MAGIC std::byteswap(0x4E435A424C4F434BUL)
|
||||
|
||||
#define NCZ_SECTION_OFFSET (0x4000 + sizeof(ncz::Header))
|
||||
|
||||
struct Header {
|
||||
u64 magic; // NCZ_SECTION_MAGIC
|
||||
u64 total_sections;
|
||||
};
|
||||
|
||||
struct BlockHeader {
|
||||
u64 magic; // NCZ_BLOCK_MAGIC
|
||||
u8 version;
|
||||
u8 type;
|
||||
u8 padding;
|
||||
u8 block_size_exponent;
|
||||
u32 total_blocks;
|
||||
u64 decompressed_size;
|
||||
};
|
||||
|
||||
struct Block {
|
||||
u32 size;
|
||||
};
|
||||
|
||||
struct BlockInfo {
|
||||
u64 offset; // compressed offset.
|
||||
u64 size; // compressed size.
|
||||
|
||||
auto InRange(u64 off) const -> bool {
|
||||
return off < offset + size && off >= offset;
|
||||
}
|
||||
};
|
||||
|
||||
struct Section {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u64 crypto_type;
|
||||
u64 padding;
|
||||
u8 key[0x10];
|
||||
u8 counter[0x10];
|
||||
|
||||
auto InRange(u64 off) const -> bool {
|
||||
return off < offset + size && off >= offset;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sphaira::ncz
|
||||
62
sphaira/include/yati/nx/npdm.hpp
Normal file
62
sphaira/include/yati/nx/npdm.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::npdm {
|
||||
|
||||
struct Meta {
|
||||
u32 magic; // "META"
|
||||
u32 signature_key_generation; // +9.0.0
|
||||
u32 _0x8;
|
||||
u8 flags;
|
||||
u8 _0xD;
|
||||
u8 main_thread_priority;
|
||||
u8 main_thread_core_num;
|
||||
u32 _0x10;
|
||||
u32 sys_resource_size; // +3.0.0
|
||||
u32 version;
|
||||
u32 main_thread_stack_size;
|
||||
char title_name[0x10];
|
||||
char product_code[0x10];
|
||||
u8 _0x40[0x30];
|
||||
u32 aci0_offset;
|
||||
u32 aci0_size;
|
||||
u32 acid_offset;
|
||||
u32 acid_size;
|
||||
};
|
||||
|
||||
struct Acid {
|
||||
u8 rsa_sig[0x100];
|
||||
u8 rsa_pub[0x100];
|
||||
u32 magic; // "ACID"
|
||||
u32 size;
|
||||
u8 version;
|
||||
u8 _0x209[0x1];
|
||||
u8 _0x20A[0x2];
|
||||
u32 flags;
|
||||
u64 program_id_min;
|
||||
u64 program_id_max;
|
||||
u32 fac_offset;
|
||||
u32 fac_size;
|
||||
u32 sac_offset;
|
||||
u32 sac_size;
|
||||
u32 kac_offset;
|
||||
u32 kac_size;
|
||||
u8 _0x238[0x8];
|
||||
};
|
||||
|
||||
struct Aci0 {
|
||||
u32 magic; // "ACI0"
|
||||
u8 _0x4[0xC];
|
||||
u64 program_id;
|
||||
u8 _0x18[0x8];
|
||||
u32 fac_offset;
|
||||
u32 fac_size;
|
||||
u32 sac_offset;
|
||||
u32 sac_size;
|
||||
u32 kac_offset;
|
||||
u32 kac_size;
|
||||
u8 _0x38[0x8];
|
||||
};
|
||||
|
||||
} // namespace sphaira::npdm
|
||||
22
sphaira/include/yati/nx/ns.hpp
Normal file
22
sphaira/include/yati/nx/ns.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include "ncm.hpp"
|
||||
|
||||
namespace sphaira::ns {
|
||||
|
||||
enum ApplicationRecordType {
|
||||
// installed
|
||||
ApplicationRecordType_Installed = 0x3,
|
||||
// application is gamecard, but gamecard isn't insterted
|
||||
ApplicationRecordType_GamecardMissing = 0x5,
|
||||
// archived
|
||||
ApplicationRecordType_Archived = 0xB,
|
||||
};
|
||||
|
||||
Result PushApplicationRecord(Service* srv, u64 tid, const ncm::ContentStorageRecord* records, u32 count);
|
||||
Result ListApplicationRecordContentMeta(Service* srv, u64 offset, u64 tid, ncm::ContentStorageRecord* out_records, u32 count, s32* entries_read);
|
||||
Result DeleteApplicationRecord(Service* srv, u64 tid);
|
||||
Result InvalidateApplicationControlCache(Service* srv, u64 tid);
|
||||
|
||||
} // namespace sphaira::ns
|
||||
62
sphaira/include/yati/nx/nxdumptool_rsa.h
Normal file
62
sphaira/include/yati/nx/nxdumptool_rsa.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* rsa.c
|
||||
*
|
||||
* Copyright (c) 2018-2019, SciresM.
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nxdumptool 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __RSA_H__
|
||||
#define __RSA_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define RSA2048_BYTES 0x100
|
||||
#define RSA2048_BITS (RSA2048_BYTES * 8)
|
||||
|
||||
#define RSA2048_SIG_SIZE RSA2048_BYTES
|
||||
#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 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, \
|
||||
size_t private_exponent_size, const void *label, size_t label_size, size_t *out_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __RSA_H__ */
|
||||
265
sphaira/include/yati/nx/tik.h
Normal file
265
sphaira/include/yati/nx/tik.h
Normal file
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* tik.h
|
||||
*
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nxdumptool 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __TIK_H__
|
||||
#define __TIK_H__
|
||||
|
||||
#include "signature.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#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 TIK_FORMAT_VERSION 2
|
||||
|
||||
#define GENERATE_TIK_STRUCT(sigtype, tiksize) \
|
||||
typedef struct { \
|
||||
SignatureBlock##sigtype sig_block; \
|
||||
TikCommonBlock tik_common_block; \
|
||||
u8 es_section_record_data[]; \
|
||||
} TikSig##sigtype; \
|
||||
NXDT_ASSERT(TikSig##sigtype, tiksize);
|
||||
|
||||
typedef enum {
|
||||
TikTitleKeyType_Common = 0,
|
||||
TikTitleKeyType_Personalized = 1,
|
||||
TikTitleKeyType_Count = 2 ///< Total values supported by this enum.
|
||||
} TikTitleKeyType;
|
||||
|
||||
typedef enum {
|
||||
TikLicenseType_Permanent = 0,
|
||||
TikLicenseType_Demo = 1,
|
||||
TikLicenseType_Trial = 2,
|
||||
TikLicenseType_Rental = 3,
|
||||
TikLicenseType_Subscription = 4,
|
||||
TikLicenseType_Service = 5,
|
||||
TikLicenseType_Count = 6 ///< Total values supported by this enum.
|
||||
} TikLicenseType;
|
||||
|
||||
typedef enum {
|
||||
TikPropertyMask_None = 0,
|
||||
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 is available after reboot. Can be encrypted.
|
||||
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;
|
||||
|
||||
/// Placed after the ticket signature block.
|
||||
typedef struct {
|
||||
char issuer[0x40];
|
||||
u8 titlekey_block[0x100];
|
||||
u8 format_version; ///< Always matches TIK_FORMAT_VERSION.
|
||||
u8 titlekey_type; ///< TikTitleKeyType.
|
||||
u16 ticket_version;
|
||||
u8 license_type; ///< TikLicenseType.
|
||||
u8 key_generation; ///< NcaKeyGeneration.
|
||||
u16 property_mask; ///< TikPropertyMask.
|
||||
u8 reserved[0x8];
|
||||
u64 ticket_id;
|
||||
u64 device_id;
|
||||
FsRightsId rights_id;
|
||||
u32 account_id;
|
||||
u32 sect_total_size;
|
||||
u32 sect_hdr_offset;
|
||||
u16 sect_hdr_count;
|
||||
u16 sect_hdr_entry_size;
|
||||
} TikCommonBlock;
|
||||
|
||||
NXDT_ASSERT(TikCommonBlock, 0x180);
|
||||
|
||||
/// ESV1/ESV2 section records are placed right after the ticket data. These aren't available in TikTitleKeyType_Common tickets.
|
||||
/// These are only used if the sect_* fields from the common block are non-zero (other than 'sect_hdr_offset').
|
||||
/// Each ESV2 section record is followed by a 'record_count' number of ESV1 records, each one of 'record_size' size.
|
||||
|
||||
typedef enum {
|
||||
TikSectionType_None = 0,
|
||||
TikSectionType_Permanent = 1,
|
||||
TikSectionType_Subscription = 2,
|
||||
TikSectionType_Content = 3,
|
||||
TikSectionType_ContentConsumption = 4,
|
||||
TikSectionType_AccessTitle = 5,
|
||||
TikSectionType_LimitedResource = 6,
|
||||
TikSectionType_Count = 7 ///< Total values supported by this enum.
|
||||
} TikSectionType;
|
||||
|
||||
typedef struct {
|
||||
u32 sect_offset;
|
||||
u32 record_size;
|
||||
u32 section_size;
|
||||
u16 record_count;
|
||||
u16 section_type; ///< TikSectionType.
|
||||
} TikESV2SectionRecord;
|
||||
|
||||
/// Used with TikSectionType_Permanent.
|
||||
typedef struct {
|
||||
u8 ref_id[0x10];
|
||||
u32 ref_id_attr;
|
||||
} TikESV1PermanentRecord;
|
||||
|
||||
/// Used with TikSectionType_Subscription.
|
||||
typedef struct {
|
||||
u32 limit;
|
||||
u8 ref_id[0x10];
|
||||
u32 ref_id_attr;
|
||||
} TikESV1SubscriptionRecord;
|
||||
|
||||
/// Used with TikSectionType_Content.
|
||||
typedef struct {
|
||||
u32 offset;
|
||||
u8 access_mask[0x80];
|
||||
} TikESV1ContentRecord;
|
||||
|
||||
/// Used with TikSectionType_ContentConsumption.
|
||||
typedef struct {
|
||||
u16 index;
|
||||
u16 code;
|
||||
u32 limit;
|
||||
} TikESV1ContentConsumptionRecord;
|
||||
|
||||
/// Used with TikSectionType_AccessTitle.
|
||||
typedef struct {
|
||||
u64 access_title_id;
|
||||
u64 access_title_mask;
|
||||
} TikESV1AccessTitleRecord;
|
||||
|
||||
/// Used with TikSectionType_LimitedResource.
|
||||
typedef struct {
|
||||
u32 limit;
|
||||
u8 ref_id[0x10];
|
||||
u32 ref_id_attr;
|
||||
} TikESV1LimitedResourceRecord;
|
||||
|
||||
/// All tickets generated below use a little endian sig_type field.
|
||||
GENERATE_TIK_STRUCT(Rsa4096, 0x3C0); /// RSA-4096 signature.
|
||||
GENERATE_TIK_STRUCT(Rsa2048, 0x2C0); /// RSA-2048 signature.
|
||||
GENERATE_TIK_STRUCT(Ecc480, 0x200); /// ECC signature.
|
||||
GENERATE_TIK_STRUCT(Hmac160, 0x1C0); /// HMAC signature.
|
||||
|
||||
/// Ticket type.
|
||||
typedef enum {
|
||||
TikType_None = 0,
|
||||
TikType_SigRsa4096 = 1,
|
||||
TikType_SigRsa2048 = 2,
|
||||
TikType_SigEcc480 = 3,
|
||||
TikType_SigHmac160 = 4,
|
||||
TikType_Count = 5 ///< Total values supported by this enum.
|
||||
} TikType;
|
||||
|
||||
/// Used to store ticket type, size and raw data, as well as titlekey data.
|
||||
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.
|
||||
/// 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.
|
||||
/// If both 'out_raw_cert_chain' and 'out_raw_cert_chain_size' pointers are provided, raw certificate chain data will be saved to them.
|
||||
/// 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 for signed ticket blobs.
|
||||
|
||||
NX_INLINE TikCommonBlock *tikGetCommonBlockFromSignedTicketBlob(void *buf)
|
||||
{
|
||||
return (TikCommonBlock*)signatureGetPayloadFromSignedBlob(buf, false);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for(u32 i = 0; i < tik_common_block->sect_hdr_count; i++)
|
||||
{
|
||||
TikESV2SectionRecord *rec = (TikESV2SectionRecord*)((u8*)tik_common_block + offset);
|
||||
offset += (sizeof(TikESV2SectionRecord) + ((u64)rec->record_count * (u64)rec->record_size));
|
||||
out_size += offset;
|
||||
}
|
||||
|
||||
return out_size;
|
||||
}
|
||||
|
||||
NX_INLINE bool tikIsValidSignedTicketBlob(void *buf)
|
||||
{
|
||||
u64 ticket_size = (signatureGetBlockSizeFromSignedBlob(buf, false) + sizeof(TikCommonBlock));
|
||||
return (ticket_size > sizeof(TikCommonBlock) && (ticket_size + tikGetSectionRecordsSizeFromSignedTicketBlob(buf)) <= SIGNED_TIK_MAX_SIZE);
|
||||
}
|
||||
|
||||
NX_INLINE u64 tikGetSignedTicketBlobSize(void *buf)
|
||||
{
|
||||
return (tikIsValidSignedTicketBlob(buf) ? (signatureGetBlockSizeFromSignedBlob(buf, false) + sizeof(TikCommonBlock) + tikGetSectionRecordsSizeFromSignedTicketBlob(buf)) : 0);
|
||||
}
|
||||
|
||||
NX_INLINE u64 tikGetSignedTicketBlobHashAreaSize(void *buf)
|
||||
{
|
||||
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 = 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
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __TIK_H__ */
|
||||
Reference in New Issue
Block a user