Merge pull request #10 from jakcron/nca-development

[nx|nstool] Nca development
This commit is contained in:
Jack 2018-05-11 17:54:25 +08:00 committed by GitHub
commit c7d78d0f1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 863 additions and 48 deletions

View file

@ -83,6 +83,7 @@ namespace nx
void setContentIndex(uint32_t index);
uint32_t getSdkAddonVersion() const;
void setSdkAddonVersion(uint32_t version);
bool hasRightsId() const;
const byte_t* getRightsId() const;
void setRightsId(const byte_t* rights_id);
const fnd::List<sPartition>& getPartitions() const;

View file

@ -9,5 +9,6 @@ namespace nx
static inline size_t sectorToOffset(size_t sector_index) { return sector_index * nx::nca::kSectorSize; }
static void decryptNcaHeader(const byte_t* src, byte_t* dst, const crypto::aes::sAesXts128Key& key);
static byte_t getMasterKeyRevisionFromKeyGeneration(byte_t key_generation);
static void getNcaPartitionAesCtr(const nx::sNcaFsHeader* hdr, byte_t* ctr);
};
}

View file

@ -0,0 +1,27 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <crypto/sha.h>
#include <fnd/ISerialiseableBinary.h>
namespace nx
{
namespace hierarchicalsha256
{
static const size_t kDefaultLevelNum = 2;
}
#pragma pack(push,1)
struct sHierarchicalSha256Header
{
crypto::sha::sSha256Hash master_hash;
le_uint32_t hash_block_size;
le_uint32_t hash_level_num;
struct sLayout
{
le_uint64_t offset;
le_uint64_t size;
} hash_data, hash_target;
};
#pragma pack(pop)
}

View file

@ -11,7 +11,7 @@ namespace nx
namespace ivfc
{
const std::string kIvfcSig = "IVFC";
static const size_t kMaxIvfcLevel = 4;
static const size_t kMaxIvfcLevel = 7;
static const uint32_t kIvfcId = 0x20000;
}
@ -24,13 +24,13 @@ namespace nx
le_uint32_t level_num;
struct sIvfcLevelHeader
{
uint64_t logical_offset;
uint64_t hash_data_size;
uint32_t block_size;
le_uint64_t logical_offset;
le_uint64_t hash_data_size;
le_uint32_t block_size;
byte_t reserved[4];
} level_header[ivfc::kMaxIvfcLevel];
byte_t unk_0xA0[0x20];
byte_t master_hash[0x20];
byte_t reserved_00[0x8];
crypto::sha::sSha256Hash master_hash;
};
#pragma pack(pop)
}

View file

@ -3,8 +3,10 @@
#include <fnd/types.h>
#include <crypto/aes.h>
#include <crypto/sha.h>
#include <crypto/rsa.h>
#include <fnd/ISerialiseableBinary.h>
#include <nx/ivfc.h>
#include <nx/hierarchicalsha256.h>
namespace nx
{
@ -19,6 +21,8 @@ namespace nx
static const size_t kAesKeyNum = 16;
static const size_t kRightsIdLen = 0x10;
static const size_t kKeyAreaEncryptionKeyNum = 3;
static const size_t kFsHeaderHashSuperblockLen = 0x130;
static const uint16_t kDefaultFsHeaderVersion = 2;
enum ProgramPartitionId
{
@ -40,6 +44,7 @@ namespace nx
TYPE_CONTROL,
TYPE_MANUAL,
TYPE_DATA,
TYPE_PUBLIC_DATA
};
enum KeyBankIndex
@ -67,7 +72,7 @@ namespace nx
enum HashType
{
HASH_AUTO,
HASH_UNK1,
HASH_NONE,
HASH_HIERARCHICAL_SHA256,
HASH_HIERARCHICAL_INTERGRITY // IVFC
};
@ -78,7 +83,7 @@ namespace nx
CRYPT_NONE,
CRYPT_AESXTS,
CRYPT_AESCTR,
CRYPT_BKTR
CRYPT_AESCTREX
};
}
@ -88,7 +93,7 @@ namespace nx
char signature[4];
byte_t distribution_type;
byte_t content_type;
byte_t key_generation; // KeyGeneration
byte_t key_generation;
byte_t key_area_encryption_key_index;
le_uint64_t content_size;
le_uint64_t program_id;
@ -110,23 +115,26 @@ namespace nx
struct sNcaFsHeader
{
le_uint16_t version; // usually 0x0002
byte_t format_type; // RomFs(0x00), PartitionFs(0x01)
byte_t hash_type; // HashTypeAuto(0x00), HashTypeHierarchicalSha256(0x02), HashTypeHierarchicalIntegrity(0x03).RomFs uses (0x03) this is forced, PartitionFs uses (0x02).
byte_t encryption_type; // EncryptionTypeAuto(0x00), EncryptionTypeNone(0x01), EncryptionTypeAesCtr(0x03)
byte_t reserved[3];
le_uint16_t version;
byte_t format_type;
byte_t hash_type;
byte_t encryption_type;
byte_t reserved_0[3];
union {
byte_t hash_superblock[nca::kFsHeaderHashSuperblockLen];
nx::sHierarchicalSha256Header hierarchicalsha256_header;
nx::sIvfcHeader ivfc_header;
};
crypto::aes::sAesIvCtr base_ctr;
byte_t reserved_1[0xB8];
};
struct sHierarchicalSha256Header
struct sNcaHeaderBlock
{
byte_t master_hash[0x20];
le_uint32_t hash_block_size;
le_uint32_t unk_0x02;
struct sLayout
{
le_uint64_t offset;
le_uint64_t size;
} hash_data, hash_target;
byte_t signature_main[crypto::rsa::kRsa2048Size];
byte_t signature_acid[crypto::rsa::kRsa2048Size];
sNcaHeader header;
sNcaFsHeader fs_header[nx::nca::kPartitionNum];
};
#pragma pack(pop)

View file

@ -219,6 +219,19 @@ void nx::NcaHeader::setSdkAddonVersion(uint32_t version)
mSdkAddonVersion = version;
}
bool nx::NcaHeader::hasRightsId() const
{
bool rightsIdIsSet = false;
for (size_t i = 0; i < nca::kRightsIdLen; i++)
{
if (mRightsId[i] != 0)
rightsIdIsSet = true;
}
return rightsIdIsSet;
}
const byte_t* nx::NcaHeader::getRightsId() const
{
return mRightsId;

View file

@ -46,4 +46,12 @@ byte_t nx::NcaUtils::getMasterKeyRevisionFromKeyGeneration(byte_t key_generation
}
return masterkey_rev;
}
void nx::NcaUtils::getNcaPartitionAesCtr(const nx::sNcaFsHeader* hdr, byte_t* ctr)
{
for (size_t i = 0; i < 16; i++)
{
ctr[15-i] = hdr->base_ctr.iv[i];
}
}

View file

@ -0,0 +1,71 @@
#include "AesCtrWrappedIFile.h"
AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) :
mFile(file),
mKey(key),
mBaseCtr(ctr)
{
mScratch.alloc(kAesCtrScratchAllocSize);
}
size_t AesCtrWrappedIFile::size()
{
return mFile.size();
}
void AesCtrWrappedIFile::seek(size_t offset)
{
mFile.seek(offset);
crypto::aes::AesIncrementCounter(mBaseCtr.iv, offset>>4, mCurrentCtr.iv);
mBlockOffset = offset & 0xf;
}
void AesCtrWrappedIFile::read(byte_t* out, size_t len)
{
for (size_t i = 0; i < (len / kAesCtrScratchSize); i++)
{
mFile.read(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
memcpy(out + (i * kAesCtrScratchSize), mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
}
if (len % kAesCtrScratchSize)
{
size_t read_len = len % kAesCtrScratchSize;
size_t read_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize);
mFile.read(mScratch.getBytes() + mBlockOffset, read_len);
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
memcpy(out + read_pos, mScratch.getBytes() + mBlockOffset, read_len);
}
}
void AesCtrWrappedIFile::read(byte_t* out, size_t offset, size_t len)
{
seek(offset);
read(out, len);
}
void AesCtrWrappedIFile::write(const byte_t* out, size_t len)
{
for (size_t i = 0; i < (len / kAesCtrScratchSize); i++)
{
memcpy(mScratch.getBytes() + mBlockOffset, out + (i * kAesCtrScratchSize), kAesCtrScratchSize);
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
mFile.write(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
}
if (len % kAesCtrScratchSize)
{
size_t write_len = len % kAesCtrScratchSize;
size_t write_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize);
memcpy(mScratch.getBytes() + mBlockOffset, out + write_pos, write_len);
crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
mFile.write(mScratch.getBytes() + mBlockOffset, write_len);
}
}
void AesCtrWrappedIFile::write(const byte_t* out, size_t offset, size_t len)
{
seek(offset);
write(out, len);
}

View file

@ -0,0 +1,27 @@
#include <fnd/IFile.h>
#include <fnd/MemoryBlob.h>
#include <crypto/aes.h>
class AesCtrWrappedIFile : public fnd::IFile
{
public:
AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr);
size_t size();
void seek(size_t offset);
void read(byte_t* out, size_t len);
void read(byte_t* out, size_t offset, size_t len);
void write(const byte_t* out, size_t len);
void write(const byte_t* out, size_t offset, size_t len);
private:
const std::string kModuleName = "AesCtrWrappedIFile";
static const size_t kAesCtrScratchSize = 0x1000000;
static const size_t kAesCtrScratchAllocSize = kAesCtrScratchSize + crypto::aes::kAesBlockSize;
fnd::IFile& mFile;
crypto::aes::sAes128Key mKey;
crypto::aes::sAesIvCtr mBaseCtr, mCurrentCtr;
size_t mBlockOffset;
fnd::MemoryBlob mScratch;
};

View file

@ -0,0 +1,16 @@
#pragma once
#include <string>
#include <fnd/types.h>
#include <fnd/SimpleFile.h>
#include <nx/NcaHeader.h>
#include "nstool.h"
class NcaPartitionProcess
{
public:
NcaPartitionProcess();
private:
const std::string kModuleName = "NcaPartitionProcess";
};

View file

@ -0,0 +1,476 @@
#include <fnd/SimpleTextOutput.h>
#include <nx/NcaUtils.h>
#include <nx/AesKeygen.h>
#include "NcaProcess.h"
#include "PfsProcess.h"
#include "RomfsProcess.h"
#include "OffsetAdjustedIFile.h"
#include "AesCtrWrappedIFile.h"
std::string kFormatVersionStr[]
{
"NCA2",
"NCA3"
};
std::string kDistributionTypeStr[]
{
"Download",
"Game Card"
};
std::string kContentTypeStr[]
{
"Program",
"Meta",
"Control",
"Manual",
"Data",
"PublicData"
};
std::string kEncryptionTypeStr[]
{
"Auto",
"None",
"AesXts",
"AesCtr",
"AesCtrEx"
};
std::string kHashTypeStr[]
{
"Auto",
"None",
"HierarchicalSha256",
"HierarchicalIntegrity"
};
std::string kFormatTypeStr[]
{
"RomFs",
"PartitionFs"
};
std::string kKaekIndexStr[]
{
"Application",
"Ocean",
"System"
};
void NcaProcess::displayHeader()
{
crypto::aes::sAes128Key zero_key;
memset(zero_key.key, 0, sizeof(zero_key));
printf("[NCA Header]\n");
printf(" Format Type: %s\n", kFormatVersionStr[mHdr.getFormatVersion()].c_str());
printf(" Dist. Type: %s\n", kDistributionTypeStr[mHdr.getDistributionType()].c_str());
printf(" Content Type: %s\n", kContentTypeStr[mHdr.getContentType()].c_str());
printf(" Key Generation: %d\n", mHdr.getKeyGeneration());
printf(" Kaek Index: %s (%d)\n", kKaekIndexStr[mHdr.getKaekIndex()].c_str(), mHdr.getKaekIndex());
printf(" Size: 0x%" PRIx64 "\n", mHdr.getContentSize());
printf(" ProgID: 0x%016" PRIx64 "\n", mHdr.getProgramId());
printf(" Content Index: %" PRIu32 "\n", mHdr.getContentIndex());
uint32_t ver = mHdr.getSdkAddonVersion();
printf(" SdkAddon Ver.: v%d.%d.%d (v%" PRIu32 ")\n", (ver>>24 & 0xff),(ver>>16 & 0xff),(ver>>8 & 0xff), ver);
printf(" RightsId: ");
fnd::SimpleTextOutput::hexDump(mHdr.getRightsId(), nx::nca::kRightsIdLen);
printf(" Key Area Keys: (Encrypted)\n");
for (size_t i = 0; i < mHdr.getEncAesKeys().getSize(); i++)
{
if (mHdr.getEncAesKeys()[i] != zero_key)
{
printf(" %2lu: ", i);
fnd::SimpleTextOutput::hexDump(mHdr.getEncAesKeys()[i].key, crypto::aes::kAes128KeySize);
}
}
/*
if (mBodyKeyList.getSize() > 0)
{
printf(" Key Area Keys:\n");
for (size_t i = 0; i < mBodyKeyList.getSize(); i++)
{
printf(" %2lu: ", i);
fnd::SimpleTextOutput::hexDump(mBodyKeyList[i].key, crypto::aes::kAes128KeySize);
}
}
*/
printf(" Partitions:\n");
for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++)
{
const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i];
nx::sNcaFsHeader& fs_header = mHdrBlock.fs_header[partition.index];
printf(" %lu:\n", i);
printf(" Index: %d\n", partition.index);
printf(" Offset: 0x%" PRIx64 "\n", partition.offset);
printf(" Size: 0x%" PRIx64 "\n", partition.size);
crypto::sha::sSha256Hash ncaFsHeaderHash;
crypto::sha::Sha256((byte_t*)&fs_header, sizeof(nx::sNcaFsHeader), ncaFsHeaderHash.bytes);
if (partition.hash.compare(ncaFsHeaderHash) == false)
{
throw fnd::Exception(kModuleName, "NcaFsHeader has bad sha256 hash");
}
//fnd::SimpleTextOutput::hxdStyleDump((byte_t*)&fs_header, sizeof(nx::sNcaFsHeader));
printf(" FsHeader:\n");
printf(" Version: 0x%d\n", fs_header.version.get());
printf(" Format Type: %s\n", kFormatTypeStr[fs_header.format_type].c_str());
printf(" Hash Type: %s\n", kHashTypeStr[fs_header.hash_type].c_str());
printf(" Enc. Type: %s\n", kEncryptionTypeStr[fs_header.encryption_type].c_str());
if (fs_header.encryption_type == nx::nca::CRYPT_AESCTR)
{
printf(" CTR: ");
crypto::aes::sAesIvCtr ctr;
nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, ctr.iv);
crypto::aes::AesIncrementCounter(ctr.iv, partition.offset>>4, ctr.iv);
fnd::SimpleTextOutput::hexDump(ctr.iv, sizeof(crypto::aes::sAesIvCtr));
}
if (fs_header.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY)
{
printf(" HierarchicalIntegrity Header:\n");
printf(" Id: 0x%x\n", fs_header.ivfc_header.id.get());
printf(" MasterHashSize: 0x%x\n", fs_header.ivfc_header.master_hash_size.get());
printf(" LevelNum: %d\n", fs_header.ivfc_header.level_num.get());
for (size_t i = 0; i < fs_header.ivfc_header.level_num.get(); i++)
{
printf(" Level %d:\n", i);
printf(" LogicalOffset: 0x%" PRIx64 "\n", fs_header.ivfc_header.level_header[i].logical_offset.get());
printf(" HashDataSize: 0x%" PRIx64 "\n", fs_header.ivfc_header.level_header[i].hash_data_size.get());
printf(" BlockSize: 0x%" PRIx32 "\n", fs_header.ivfc_header.level_header[i].block_size.get());
}
printf(" Master Hash: ");
fnd::SimpleTextOutput::hexDump(fs_header.ivfc_header.master_hash.bytes, 0x20);
}
else if (fs_header.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256)
{
nx::sHierarchicalSha256Header& hash_hdr = fs_header.hierarchicalsha256_header;
printf(" HierarchicalSha256 Header:\n");
printf(" Master Hash: ");
fnd::SimpleTextOutput::hexDump(hash_hdr.master_hash.bytes, 0x20);
printf(" HashBlockSize: 0x%x\n", hash_hdr.hash_block_size.get());
printf(" HashLevelNum: 0x%x\n", hash_hdr.hash_level_num.get());
printf(" HashDataOffset: 0x%" PRIx64 "\n", hash_hdr.hash_data.offset.get());
printf(" HashDataSize: 0x%" PRIx64 "\n", hash_hdr.hash_data.size.get());
printf(" HashTargetOffset: 0x%" PRIx64 "\n", hash_hdr.hash_target.offset.get());
printf(" HashTargetSize: 0x%" PRIx64 "\n", hash_hdr.hash_target.size.get());
}
else
{
printf(" Hash Superblock:\n");
fnd::SimpleTextOutput::hxdStyleDump(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen);
}
}
}
void NcaProcess::generateNcaBodyEncryptionKeys()
{
// create zeros key
crypto::aes::sAes128Key zero_aesctr_key;
memset(zero_aesctr_key.key, 0, sizeof(zero_aesctr_key));
crypto::aes::sAesXts128Key zero_aesxts_key;
memset(zero_aesxts_key.key, 0, sizeof(zero_aesxts_key));
// get key data from header
byte_t masterkey_rev = nx::NcaUtils::getMasterKeyRevisionFromKeyGeneration(mHdr.getKeyGeneration());
byte_t keak_index = mHdr.getKaekIndex();
// set flag to indicate that the keys are not available
mBodyKeys.aes_ctr.isSet = false;
mBodyKeys.aes_xts.isSet = false;
// if this has a rights id, the key needs to be sourced from a ticket
if (mHdr.hasRightsId() == true)
{
// if the titlekey_kek is available
if (mKeyset->ticket.titlekey_kek[masterkey_rev] != zero_aesctr_key)
{
// the title key is provided (sourced from ticket)
if (mKeyset->nca.manual_title_key_aesctr != zero_aesctr_key)
{
nx::AesKeygen::generateKey(mBodyKeys.aes_ctr.var.key, mKeyset->nca.manual_title_key_aesctr.key, mKeyset->ticket.titlekey_kek[masterkey_rev].key);
mBodyKeys.aes_ctr.isSet = true;
}
if (mKeyset->nca.manual_title_key_aesxts != zero_aesxts_key)
{
nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[0], mKeyset->nca.manual_title_key_aesxts.key[0], mKeyset->ticket.titlekey_kek[masterkey_rev].key);
nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[1], mKeyset->nca.manual_title_key_aesxts.key[1], mKeyset->ticket.titlekey_kek[masterkey_rev].key);
mBodyKeys.aes_xts.isSet = true;
}
}
}
// otherwise decrypt key area
else
{
// if the titlekey_kek is available
if (mKeyset->nca.key_area_key[keak_index][masterkey_rev] != zero_aesctr_key)
{
nx::AesKeygen::generateKey(mBodyKeys.aes_ctr.var.key, mHdr.getEncAesKeys()[nx::nca::KEY_AESCTR].key, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key);
mBodyKeys.aes_ctr.isSet = true;
nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[0], mHdr.getEncAesKeys()[nx::nca::KEY_AESXTS_0].key, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key);
nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[1], mHdr.getEncAesKeys()[nx::nca::KEY_AESXTS_1].key, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key);
mBodyKeys.aes_xts.isSet = true;
}
}
// if the keys weren't generated, check if the keys were supplied by the user
if (mBodyKeys.aes_ctr.isSet == false && mKeyset->nca.manual_body_key_aesctr != zero_aesctr_key)
{
mBodyKeys.aes_ctr = mKeyset->nca.manual_body_key_aesctr;
}
if (mBodyKeys.aes_xts.isSet == false && mKeyset->nca.manual_body_key_aesxts != zero_aesxts_key)
{
mBodyKeys.aes_xts = mKeyset->nca.manual_body_key_aesxts;
}
}
void NcaProcess::processPartitions()
{
for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++)
{
const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i];
nx::sNcaFsHeader& fs_header = mHdrBlock.fs_header[partition.index];
crypto::aes::sAesIvCtr ctr;
nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, ctr.iv);
// create reader
fnd::IFile* partitionReader = nullptr;
AesCtrWrappedIFile aesCtrFile = AesCtrWrappedIFile(*mReader, mBodyKeys.aes_ctr.var, ctr);
switch(fs_header.encryption_type)
{
case (nx::nca::CRYPT_AESXTS):
case (nx::nca::CRYPT_AESCTREX):
partitionReader = nullptr;
break;
case (nx::nca::CRYPT_AESCTR):
partitionReader = mBodyKeys.aes_ctr.isSet? &aesCtrFile : nullptr;
break;
case (nx::nca::CRYPT_NONE):
partitionReader = mReader;
break;
}
// if the reader is null, skip
if (partitionReader == nullptr)
{
printf("[WARNING] NCA Partition %d not readable\n", partition.index);
continue;
}
size_t data_offset = 0;
switch (fs_header.hash_type)
{
case (nx::nca::HASH_HIERARCHICAL_SHA256):
data_offset = fs_header.hierarchicalsha256_header.hash_target.offset.get();
break;
case (nx::nca::HASH_HIERARCHICAL_INTERGRITY):
data_offset = fs_header.ivfc_header.level_header[5].logical_offset.get();
break;
case (nx::nca::HASH_NONE):
data_offset = 0;
break;
default:
throw fnd::Exception(kModuleName, "Unknown hash type");
}
if (fs_header.format_type == nx::nca::FORMAT_PFS0)
{
PfsProcess pfs;
pfs.setInputFile(*partitionReader);
pfs.setInputFileOffset(partition.offset + data_offset);
pfs.setCliOutputMode(mCliOutputType);
pfs.setListFs(mListFs);
if (mPartitionPath[partition.index].doExtract)
pfs.setExtractPath(mPartitionPath[partition.index].path);
pfs.process();
}
else if (fs_header.format_type == nx::nca::FORMAT_ROMFS)
{
RomfsProcess romfs;
romfs.setInputFile(*partitionReader);
romfs.setInputFileOffset(partition.offset + data_offset);
romfs.setCliOutputMode(mCliOutputType);
romfs.setListFs(mListFs);
if (mPartitionPath[partition.index].doExtract)
romfs.setExtractPath(mPartitionPath[partition.index].path);
romfs.process();
}
else
{
throw fnd::Exception(kModuleName, "Unknown format type");
}
}
}
NcaProcess::NcaProcess() :
mReader(nullptr),
mOffset(0),
mKeyset(nullptr),
mCliOutputType(OUTPUT_NORMAL),
mVerify(false),
mListFs(false)
{
mPartitionPath[0].doExtract = false;
mPartitionPath[1].doExtract = false;
mPartitionPath[2].doExtract = false;
mPartitionPath[3].doExtract = false;
}
NcaProcess::~NcaProcess()
{
}
void NcaProcess::process()
{
fnd::MemoryBlob scratch;
if (mReader == nullptr)
{
throw fnd::Exception(kModuleName, "No file reader set.");
}
// read header block
mReader->read((byte_t*)&mHdrBlock, mOffset, sizeof(nx::sNcaHeaderBlock));
// decrypt header block
nx::NcaUtils::decryptNcaHeader((byte_t*)&mHdrBlock, (byte_t*)&mHdrBlock, mKeyset->nca.header_key);
// generate header hash
crypto::sha::Sha256((byte_t*)&mHdrBlock.header, sizeof(nx::sNcaHeader), mHdrHash.bytes);
// validate signature[0]
if (mVerify)
{
if (crypto::rsa::pss::rsaVerify(mKeyset->nca.header_sign_key, crypto::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_main) != 0)
{
// this is minimal even though it's a warning because it's a validation method
if (mCliOutputType >= OUTPUT_MINIMAL)
printf("[WARNING] NCA Header Main Signature: FAIL \n");
}
}
// proccess main header
mHdr.importBinary((byte_t*)&mHdrBlock.header, sizeof(nx::sNcaHeader));
// validate fs headers
if (mVerify)
{
crypto::sha::sSha256Hash calc_hash;
for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++)
{
const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i];
crypto::sha::Sha256((const byte_t*)&mHdrBlock.fs_header[partition.index], sizeof(nx::sNcaFsHeader), calc_hash.bytes);
if (calc_hash.compare(partition.hash) == false)
{
// this is minimal even though it's a warning because it's a validation method
if (mCliOutputType >= OUTPUT_MINIMAL)
printf("[WARNING] NCA FsHeader[%d] Hash: FAIL \n", partition.index);
}
}
}
// determine keys
/*
NCA is a file container
A hashed and signed file container
To verify a NCA: (R=regular step)
1 - decrypt header (R)
2 - verify signature[0]
3 - validate hashes of fs_headers
4 - determine how to read/decrypt the partitions (R)
5 - validate the partitions depending on their hash method
6 - if this NCA is a Program or Patch, open main.npdm from partition0
7 - validate ACID
8 - use public key in ACID to verify NCA signature[1]
Things to consider
* because of the manditory steps between verifcation steps
the NCA should be ready to be pulled to pieces before any printing is done
so the verification text can be presented without interuption
*/
// decrypt key area
generateNcaBodyEncryptionKeys();
if (mCliOutputType >= OUTPUT_NORMAL)
displayHeader();
processPartitions();
}
void NcaProcess::setInputFile(fnd::IFile* reader)
{
mReader = reader;
}
void NcaProcess::setInputFileOffset(size_t offset)
{
mOffset = offset;
}
void NcaProcess::setKeyset(const sKeyset* keyset)
{
mKeyset = keyset;
}
void NcaProcess::setCliOutputMode(CliOutputType type)
{
mCliOutputType = type;
}
void NcaProcess::setVerifyMode(bool verify)
{
mVerify = verify;
}
void NcaProcess::setPartition0ExtractPath(const std::string& path)
{
mPartitionPath[0].path = path;
mPartitionPath[0].doExtract = true;
}
void NcaProcess::setPartition1ExtractPath(const std::string& path)
{
mPartitionPath[1].path = path;
mPartitionPath[1].doExtract = true;
}
void NcaProcess::setPartition2ExtractPath(const std::string& path)
{
mPartitionPath[2].path = path;
mPartitionPath[2].doExtract = true;
}
void NcaProcess::setPartition3ExtractPath(const std::string& path)
{
mPartitionPath[3].path = path;
mPartitionPath[3].doExtract = true;
}
void NcaProcess::setListFs(bool list_fs)
{
mListFs = list_fs;
}

View file

@ -2,7 +2,6 @@
#include <string>
#include <fnd/types.h>
#include <fnd/SimpleFile.h>
#include <nx/nca.h>
#include <nx/NcaHeader.h>
#include "nstool.h"
@ -16,18 +15,54 @@ public:
void process();
// generic
void setInputFile(fnd::IFile& reader);
void setInputFile(fnd::IFile* reader);
void setInputFileOffset(size_t offset);
void setKeyset(const sKeyset* keyset);
void setCliOutputMode(CliOutputType type);
void setVerifyMode(bool verify);
// nca specfic
void setPartition0ExtractPath(const std::string& path);
void setPartition1ExtractPath(const std::string& path);
void setPartition2ExtractPath(const std::string& path);
void setPartition3ExtractPath(const std::string& path);
void setListFs(bool list_fs);
private:
const std::string kModuleName = "NcaProcess";
byte_t mRawHeader[nx::nca::kHeaderSize];
std::string mPath;
// user options
fnd::IFile* mReader;
size_t mOffset;
const sKeyset* mKeyset;
CliOutputType mCliOutputType;
bool mVerify;
struct sExtract
{
std::string path;
bool doExtract;
} mPartitionPath[nx::nca::kPartitionNum];
bool mListFs;
// data
nx::sNcaHeaderBlock mHdrBlock;
crypto::sha::sSha256Hash mHdrHash;
nx::NcaHeader mHdr;
// crypto
struct sKeys
{
sOptional<crypto::aes::sAes128Key> aes_ctr;
sOptional<crypto::aes::sAesXts128Key> aes_xts;
} mBodyKeys;
void displayHeader();
void generateNcaBodyEncryptionKeys();
void processPartitions();
};

View file

@ -0,0 +1,45 @@
#include "OffsetAdjustedIFile.h"
OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t size) :
mFile(file),
mBaseOffset(offset),
mCurrentOffset(0),
mSize(size)
{
}
size_t OffsetAdjustedIFile::size()
{
return mSize;
}
void OffsetAdjustedIFile::seek(size_t offset)
{
mCurrentOffset = offset;
mFile.seek(offset + mBaseOffset);
}
void OffsetAdjustedIFile::read(byte_t* out, size_t len)
{
seek(mCurrentOffset);
mFile.read(out, len);
}
void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t len)
{
seek(offset);
read(out, len);
}
void OffsetAdjustedIFile::write(const byte_t* out, size_t len)
{
seek(mCurrentOffset);
mFile.write(out, len);
}
void OffsetAdjustedIFile::write(const byte_t* out, size_t offset, size_t len)
{
seek(offset);
write(out, len);
}

View file

@ -0,0 +1,18 @@
#include <fnd/IFile.h>
class OffsetAdjustedIFile : public fnd::IFile
{
public:
OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t size);
size_t size();
void seek(size_t offset);
void read(byte_t* out, size_t len);
void read(byte_t* out, size_t offset, size_t len);
void write(const byte_t* out, size_t len);
void write(const byte_t* out, size_t offset, size_t len);
private:
fnd::IFile& mFile;
size_t mBaseOffset, mCurrentOffset;
size_t mSize;
};

View file

@ -73,9 +73,13 @@ void PfsProcess::extractFs()
fnd::SimpleFile outFile;
const fnd::List<nx::PfsHeader::sFile>& file = mPfs.getFileList();
std::string file_path;
for (size_t i = 0; i < file.getSize(); i++)
{
outFile.open(mExtractPath + kPathSeparator + file[i].name, outFile.Create);
file_path.clear();
fnd::io::appendToPath(file_path, mExtractPath);
fnd::io::appendToPath(file_path, file[i].name);
outFile.open(file_path, outFile.Create);
mReader->seek(mOffset + file[i].offset);
for (size_t j = 0; j < (file[i].size / kFileExportBlockSize); j++)
{

View file

@ -52,7 +52,6 @@ void UserSettings::showHelp()
printf(" nstool [--listfs] [--fsdir <dir>] <file>\n");
printf(" --listfs Print file system\n");
printf(" --fsdir Extract file system to directory\n");
/*
printf("\n NCA (Nintendo Content Archive)\n");
printf(" nstool [--listfs] [--bodykey <key> --titlekey <key>] [--part0 <dir> ...] <.nca file>\n");
printf(" --listfs Print file system in embedded partitions\n");
@ -62,7 +61,6 @@ void UserSettings::showHelp()
printf(" --part1 Extract \"partition 1\" to directory \n");
printf(" --part2 Extract \"partition 2\" to directory \n");
printf(" --part3 Extract \"partition 3\" to directory \n");
*/
}
const std::string UserSettings::getInputPath() const
@ -115,6 +113,26 @@ const sOptional<std::string>& UserSettings::getFsPath() const
return mFsPath;
}
const sOptional<std::string>& UserSettings::getPart0Path() const
{
return mPart0Path;
}
const sOptional<std::string>& UserSettings::getPart1Path() const
{
return mPart1Path;
}
const sOptional<std::string>& UserSettings::getPart2Path() const
{
return mPart2Path;
}
const sOptional<std::string>& UserSettings::getPart3Path() const
{
return mPart3Path;
}
void UserSettings::populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args)
{
@ -226,10 +244,28 @@ void UserSettings::populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args)
cmd_args.nca_bodykey = args[i+1];
}
else if (args[i] == "-o")
else if (args[i] == "--part0")
{
if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
cmd_args.output_path = args[i+1];
cmd_args.part0_path = args[i+1];
}
else if (args[i] == "--part1")
{
if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
cmd_args.part1_path = args[i+1];
}
else if (args[i] == "--part2")
{
if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
cmd_args.part2_path = args[i+1];
}
else if (args[i] == "--part3")
{
if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
cmd_args.part3_path = args[i+1];
}
else
@ -393,7 +429,14 @@ void UserSettings::populateKeyset(sCmdArgs& args)
if (args.nca_titlekey.isSet)
{
decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key.key, sizeof(crypto::aes::sAes128Key));
if (args.nca_bodykey.var.length() == (sizeof(crypto::aes::sAes128Key)*2))
{
decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesctr.key, sizeof(crypto::aes::sAes128Key));
}
else
{
decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesxts.key[0], sizeof(crypto::aes::sAesXts128Key));
}
}
#undef _SAVE_KEYDATA
@ -475,6 +518,10 @@ void UserSettings::populateUserSettings(sCmdArgs& args)
mNormalPath = args.normal_path;
mSecurePath = args.secure_path;
mFsPath = args.fs_path;
mPart0Path = args.part0_path;
mPart1Path = args.part1_path;
mPart2Path = args.part2_path;
mPart3Path = args.part3_path;
// determine output path
if (args.verbose_output.isSet)

View file

@ -26,6 +26,10 @@ public:
const sOptional<std::string>& getNormalPath() const;
const sOptional<std::string>& getSecurePath() const;
const sOptional<std::string>& getFsPath() const;
const sOptional<std::string>& getPart0Path() const;
const sOptional<std::string>& getPart1Path() const;
const sOptional<std::string>& getPart2Path() const;
const sOptional<std::string>& getPart3Path() const;
private:
const std::string kModuleName = "UserSettings";
@ -33,7 +37,6 @@ private:
struct sCmdArgs
{
sOptional<std::string> input_path;
sOptional<std::string> output_path;
sOptional<bool> devkit_keys;
sOptional<std::string> keyset_path;
sOptional<std::string> file_type;
@ -47,11 +50,14 @@ private:
sOptional<std::string> fs_path;
sOptional<std::string> nca_titlekey;
sOptional<std::string> nca_bodykey;
sOptional<std::string> part0_path;
sOptional<std::string> part1_path;
sOptional<std::string> part2_path;
sOptional<std::string> part3_path;
void clear()
{
input_path.isSet = false;
output_path.isSet = false;
devkit_keys.isSet = false;
keyset_path.isSet = false;
file_type.isSet = false;
@ -65,6 +71,10 @@ private:
fs_path.isSet = false;
nca_titlekey.isSet = false;
nca_bodykey.isSet = false;
part0_path.isSet = false;
part1_path.isSet = false;
part2_path.isSet = false;
part3_path.isSet = false;
}
};
@ -80,6 +90,11 @@ private:
sOptional<std::string> mSecurePath;
sOptional<std::string> mFsPath;
sOptional<std::string> mPart0Path;
sOptional<std::string> mPart1Path;
sOptional<std::string> mPart2Path;
sOptional<std::string> mPart3Path;
void populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args);
void populateKeyset(sCmdArgs& args);
void populateUserSettings(sCmdArgs& args);

View file

@ -4,7 +4,7 @@
#include "XciProcess.h"
#include "PfsProcess.h"
#include "RomfsProcess.h"
//#include "NcaProcess.h"
#include "NcaProcess.h"
#include "NpdmProcess.h"
@ -54,7 +54,6 @@ int main(int argc, char** argv)
}
else if (user_set.getFileType() == FILE_ROMFS)
{
RomfsProcess romfs;
romfs.setInputFile(inputFile);
@ -67,20 +66,28 @@ int main(int argc, char** argv)
romfs.setListFs(user_set.isListFs());
romfs.process();
}
else if (user_set.getFileType() == FILE_NCA)
{
/*
NcaProcess nca;
nca.setNcaPath(user_set.getInputPath());
nca.setKeyset(user_set.getKeyset());
nca.setInputFile(&inputFile);
nca.setKeyset(&user_set.getKeyset());
nca.setCliOutputMode(user_set.getCliOutputType());
nca.setVerifyMode(user_set.isVerifyFile());
if (user_set.getPart0Path().isSet)
nca.setPartition0ExtractPath(user_set.getPart0Path().var);
if (user_set.getPart1Path().isSet)
nca.setPartition1ExtractPath(user_set.getPart1Path().var);
if (user_set.getPart2Path().isSet)
nca.setPartition2ExtractPath(user_set.getPart2Path().var);
if (user_set.getPart3Path().isSet)
nca.setPartition3ExtractPath(user_set.getPart3Path().var);
nca.setListFs(user_set.isListFs());
nca.process();
*/
}
else if (user_set.getFileType() == FILE_NPDM)
{

View file

@ -9,11 +9,6 @@
static const size_t kMasterKeyNum = 0x20;
static const size_t kNcaKeakNum = nx::nca::kKeyAreaEncryptionKeyNum;
#ifdef _WIN32
const std::string kPathSeparator = "\\";
#else
const std::string kPathSeparator = "/";
#endif
enum FileType
{
@ -63,7 +58,8 @@ struct sKeyset
crypto::aes::sAesXts128Key header_key;
crypto::aes::sAes128Key key_area_key[kNcaKeakNum][kMasterKeyNum];
crypto::aes::sAes128Key manual_title_key;
crypto::aes::sAes128Key manual_title_key_aesctr;
crypto::aes::sAesXts128Key manual_title_key_aesxts;
crypto::aes::sAes128Key manual_body_key_aesctr;
crypto::aes::sAesXts128Key manual_body_key_aesxts;
} nca;