Fixes of PS3 filesystem EDAT/SDAT handling

* Return EFSSPEIFIC if encountered corrupted EDAT or an unmatched key in sys_fs_open and sys_fs_read.
* Fix validate_dev_klic() for license-free EDAT.
* Fix EDATADecrypter::ReadData() for when size + pos > file_size but pos < file_size.
* Try to save up to 16 decryption keys. Educated guess based on NPDRM file descriptors count llimit.
* Return LICENCE_NOT_FOUND if needed RAP file is not found in sceNpDrmIsAvailable.
* Check additional sys_fs_open errors for sceNpDrmOpen. (EISDIR, ENOTMOUNTED)
This commit is contained in:
Eladash 2021-10-15 12:11:16 +03:00 committed by Ivan
parent 933d96af5f
commit c49ebba648
9 changed files with 141 additions and 105 deletions

View file

@ -549,6 +549,11 @@ int check_data(unsigned char *key, EDAT_HEADER *edat, NPD_HEADER *npd, const fs:
bool validate_dev_klic(const u8* klicensee, NPD_HEADER *npd)
{
if ((npd->license & 0x3) != 0x3)
{
return true;
}
unsigned char dev[0x60] = { 0 };
// Build the dev buffer (first 0x60 bytes of NPD header in big-endian).
@ -566,12 +571,6 @@ bool validate_dev_klic(const u8* klicensee, NPD_HEADER *npd)
u128 klic;
std::memcpy(&klic, klicensee, sizeof(klic));
if (!klic)
{
// Allow empty dev hash.
return true;
}
// Generate klicensee xor key.
u128 key = klic ^ std::bit_cast<u128>(NP_OMAC_KEY_2);
@ -579,13 +578,19 @@ bool validate_dev_klic(const u8* klicensee, NPD_HEADER *npd)
return cmac_hash_compare(reinterpret_cast<uchar*>(&key), 0x10, dev, 0x60, npd->dev_hash, 0x10);
}
bool validate_npd_hashes(const char* file_name, const u8* klicensee, NPD_HEADER *npd, bool verbose)
bool validate_npd_hashes(const char* file_name, const u8* klicensee, NPD_HEADER *npd, EDAT_HEADER* edat, bool verbose)
{
if (!file_name)
{
fmt::throw_exception("Empty filename");
}
// Ignore header validation in DEBUG data.
if (edat->flags & EDAT_DEBUG_DATA_FLAG)
{
return true;
}
const usz file_name_length = std::strlen(file_name);
const usz buf_len = 0x30 + file_name_length;
@ -704,14 +709,10 @@ bool extract_all_data(const fs::file* input, const fs::file* output, const char*
// Perform header validation (EDAT only).
char real_file_name[CRYPTO_MAX_PATH];
extract_file_name(input_file_name, real_file_name);
if (!validate_npd_hashes(real_file_name, devklic, &NPD, verbose))
if (!validate_npd_hashes(real_file_name, devklic, &NPD, &EDAT, verbose))
{
// Ignore header validation in DEBUG data.
if ((EDAT.flags & EDAT_DEBUG_DATA_FLAG) != EDAT_DEBUG_DATA_FLAG)
{
edat_log.error("NPD hash validation failed!");
return true;
}
edat_log.error("NPD hash validation failed!");
return true;
}
// Select EDAT key.
@ -784,7 +785,7 @@ u128 GetEdatRifKeyFromRapFile(const fs::file& rap_file)
return rifkey;
}
bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& input_file_name, const u8* custom_klic, std::string* contentID)
bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& input_file_name, const u8* custom_klic, std::string* contentID, u32* license)
{
// Setup NPD and EDAT/SDAT structs.
NPD_HEADER NPD;
@ -808,17 +809,17 @@ bool VerifyEDATHeaderWithKLicense(const fs::file& input, const std::string& inpu
// Perform header validation (EDAT only).
char real_file_name[CRYPTO_MAX_PATH];
extract_file_name(input_file_name.c_str(), real_file_name);
if (!validate_npd_hashes(real_file_name, custom_klic, &NPD, false))
if (!validate_npd_hashes(real_file_name, custom_klic, &NPD, &EDAT, false))
{
// Ignore header validation in DEBUG data.
if ((EDAT.flags & EDAT_DEBUG_DATA_FLAG) != EDAT_DEBUG_DATA_FLAG)
{
edat_log.error("NPD hash validation failed!");
return false;
}
edat_log.error("NPD hash validation failed!");
return false;
}
*contentID = std::string(reinterpret_cast<const char*>(NPD.content_id));
std::string_view sv{NPD.content_id, std::size(NPD.content_id)};
sv = sv.substr(0, sv.find_first_of('\0'));
if (contentID) *contentID = std::string(sv);
if (license) *license = (NPD.license & 3);
return true;
}
@ -910,33 +911,13 @@ bool EDATADecrypter::ReadHeader()
else
{
// verify key
if (!validate_dev_klic(reinterpret_cast<uchar*>(&dev_key), &npdHeader))
if (!validate_dev_klic(reinterpret_cast<uchar*>(&dec_key), &npdHeader))
{
edat_log.error("Failed validating klic");
return false;
}
// Select EDAT key.
if ((npdHeader.license & 0x3) == 0x3) // Type 3: Use supplied devklic.
dec_key = std::move(dev_key);
else if ((npdHeader.license & 0x2) == 0x2) // Type 2: Use key from RAP file (RIF key).
{
dec_key = std::move(rif_key);
if (!dec_key)
{
edat_log.warning("Empty Dec key for local activation!");
}
}
else if ((npdHeader.license & 0x1) == 0x1) // Type 1: Use network activation.
{
dec_key = std::move(rif_key);
if (!dec_key)
{
edat_log.warning("Empty Dec key for network activation!");
}
}
// Use provided dec_key
}
edata_file.seek(0);
@ -952,29 +933,37 @@ bool EDATADecrypter::ReadHeader()
file_size = edatHeader.file_size;
total_blocks = utils::aligned_div(edatHeader.file_size, edatHeader.block_size);
// Try decrypting the first block instead
u8 data_sample[1];
if (!ReadData(0, data_sample, 1))
{
return false;
}
return true;
}
u64 EDATADecrypter::ReadData(u64 pos, u8* data, u64 size)
{
if (pos > edatHeader.file_size)
return 0;
size = std::min<u64>(size, pos > edatHeader.file_size ? 0 : edatHeader.file_size - pos);
// now we need to offset things to account for the actual 'range' requested
const u64 startOffset = pos % edatHeader.block_size;
const u32 num_blocks = static_cast<u32>(utils::aligned_div(startOffset + size, edatHeader.block_size));
const u64 bufSize = num_blocks*edatHeader.block_size;
if (data_buf_size < (bufSize))
if (!size)
{
data_buf.reset(new u8[bufSize]);
data_buf_size = bufSize;
return 0;
}
// find and decrypt block range covering pos + size
const u32 starting_block = static_cast<u32>(pos / edatHeader.block_size);
const u32 ending_block = std::min(starting_block + num_blocks, total_blocks);
// Now we need to offset things to account for the actual 'range' requested
const u64 startOffset = pos % edatHeader.block_size;
const u64 num_blocks = utils::aligned_div(startOffset + size, edatHeader.block_size);
data_buf.resize(num_blocks * edatHeader.block_size);
// Find and decrypt block range covering pos + size
const u32 starting_block = ::narrow<u32>(pos / edatHeader.block_size);
const u32 ending_block = ::narrow<u32>(std::min<u64>(starting_block + num_blocks, total_blocks));
u64 writeOffset = 0;
for (u32 i = starting_block; i < ending_block; ++i)
{
edata_file.seek(0);
@ -984,6 +973,7 @@ u64 EDATADecrypter::ReadData(u64 pos, u8* data, u64 size)
edat_log.error("Error Decrypting data");
return 0;
}
writeOffset += res;
}