Added PS3 encryption.

This commit is contained in:
2026-03-15 18:32:08 +00:00
parent 608b391c96
commit 85dc9ea1b8
8 changed files with 827 additions and 1 deletions

View File

@@ -235,10 +235,13 @@ add_library(aaruformat SHARED
src/dump.c
include/aaruformat/structs/tape.h
src/blocks/tape.c
src/blocks/flux.c)
src/blocks/flux.c
src/ps3/aes128.c
src/ps3/aes128.h
src/ps3/ps3_crypto.c
src/ps3/ps3_crypto.h
src/ps3/ps3_encryption_map.c
src/ps3/ps3_encryption_map.h)
# Set up include directories for the target
target_include_directories(aaruformat

64
src/ps3/ps3_crypto.c Normal file
View File

@@ -0,0 +1,64 @@
/*
* This file is part of the Aaru Data Preservation Suite.
* Copyright (c) 2019-2026 Natalia Portillo.
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <https://www.gnu.org/licenses/>.
*
* PS3 disc encryption: key derivation, sector encrypt/decrypt, IV derivation.
*/
#include <stdint.h>
#include <string.h>
#include "aes128.h"
#include "ps3_crypto.h"
/* PS3 Encryption Round Key — publicly known constant */
static const uint8_t PS3_ERK[16] = {0x38, 0x0B, 0xCF, 0x0B, 0x53, 0x45, 0x5B, 0x3C,
0x78, 0x17, 0xAB, 0x4F, 0xA3, 0xBA, 0x90, 0xED};
/* PS3 ERK IV — publicly known constant */
static const uint8_t PS3_ERK_IV[16] = {0x69, 0x47, 0x47, 0x72, 0xAF, 0x6F, 0xDA, 0xB3,
0x42, 0x74, 0x3A, 0xEF, 0xAA, 0x18, 0x62, 0x87};
void ps3_derive_disc_key(const uint8_t data1[16], uint8_t disc_key[16])
{
memcpy(disc_key, data1, 16);
aes128_cbc_encrypt(PS3_ERK, PS3_ERK_IV, disc_key, 16);
}
void ps3_derive_iv(uint64_t sector_num, uint8_t iv[16])
{
memset(iv, 0, 16);
for(int i = 15; i >= 0 && sector_num > 0; i--)
{
iv[i] = (uint8_t)(sector_num & 0xFF);
sector_num >>= 8;
}
}
void ps3_encrypt_sector(const uint8_t disc_key[16], uint64_t sector_num, uint8_t *data, uint32_t length)
{
uint8_t iv[16];
ps3_derive_iv(sector_num, iv);
aes128_cbc_encrypt(disc_key, iv, data, length);
}
void ps3_decrypt_sector(const uint8_t disc_key[16], uint64_t sector_num, uint8_t *data, uint32_t length)
{
uint8_t iv[16];
ps3_derive_iv(sector_num, iv);
aes128_cbc_decrypt(disc_key, iv, data, length);
}

76
src/ps3/ps3_crypto.h Normal file
View File

@@ -0,0 +1,76 @@
/*
* This file is part of the Aaru Data Preservation Suite.
* Copyright (c) 2019-2026 Natalia Portillo.
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <https://www.gnu.org/licenses/>.
*
* PS3 disc encryption: key derivation, sector encrypt/decrypt, IV derivation.
*/
#ifndef LIBAARUFORMAT_PS3_CRYPTO_H
#define LIBAARUFORMAT_PS3_CRYPTO_H
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Derive a PS3 disc key from a data1 key.
*
* disc_key = AES-128-CBC-Encrypt(PS3_ERK, PS3_ERK_IV, data1)
*
* @param data1 16-byte data1 key (from disc or IRD).
* @param disc_key Output: 16-byte derived disc key.
*/
void ps3_derive_disc_key(const uint8_t data1[16], uint8_t disc_key[16]);
/**
* @brief Derive the per-sector AES IV from a sector number.
*
* IV = sector number as 128-bit big-endian integer, zero-padded left.
*
* @param sector_num Sector number.
* @param iv Output: 16-byte IV buffer.
*/
void ps3_derive_iv(uint64_t sector_num, uint8_t iv[16]);
/**
* @brief Encrypt a sector using PS3 disc encryption (AES-128-CBC).
*
* @param disc_key 16-byte disc key.
* @param sector_num Sector number (for IV derivation).
* @param data Buffer to encrypt in-place.
* @param length Number of bytes (must be multiple of 16).
*/
void ps3_encrypt_sector(const uint8_t disc_key[16], uint64_t sector_num, uint8_t *data, uint32_t length);
/**
* @brief Decrypt a sector using PS3 disc encryption (AES-128-CBC).
*
* @param disc_key 16-byte disc key.
* @param sector_num Sector number (for IV derivation).
* @param data Buffer to decrypt in-place.
* @param length Number of bytes (must be multiple of 16).
*/
void ps3_decrypt_sector(const uint8_t disc_key[16], uint64_t sector_num, uint8_t *data, uint32_t length);
#ifdef __cplusplus
}
#endif
#endif /* LIBAARUFORMAT_PS3_CRYPTO_H */

View File

@@ -0,0 +1,161 @@
/*
* This file is part of the Aaru Data Preservation Suite.
* Copyright (c) 2019-2026 Natalia Portillo.
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <https://www.gnu.org/licenses/>.
*
* PS3 encryption map: plaintext region parsing, serialization, and lookup.
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "ps3_encryption_map.h"
/* Read a big-endian uint32 from a byte buffer. */
static uint32_t read_be32(const uint8_t *p)
{
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3];
}
/* Read a little-endian uint32 from a byte buffer. */
static uint32_t read_le32(const uint8_t *p)
{
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
/* Write a little-endian uint32 to a byte buffer. */
static void write_le32(uint8_t *p, uint32_t v)
{
p[0] = (uint8_t)(v & 0xFF);
p[1] = (uint8_t)((v >> 8) & 0xFF);
p[2] = (uint8_t)((v >> 16) & 0xFF);
p[3] = (uint8_t)((v >> 24) & 0xFF);
}
int32_t ps3_parse_encryption_map(const uint8_t *sector0, uint32_t length, Ps3PlaintextRegion **regions,
uint32_t *count)
{
if(sector0 == NULL || regions == NULL || count == NULL) return -1;
/* Minimum required: 4 (region_count) + 4 (unknown) = 8 bytes */
if(length < 8) return -2;
uint32_t region_count = read_be32(sector0);
if(region_count > PS3_MAX_PLAINTEXT_REGIONS) return -3;
if(region_count == 0)
{
*regions = NULL;
*count = 0;
return 0;
}
/* Need 8 + region_count * 8 bytes */
uint32_t required = 8 + region_count * 8;
if(length < required) return -2;
Ps3PlaintextRegion *r = (Ps3PlaintextRegion *)malloc(region_count * sizeof(Ps3PlaintextRegion));
if(r == NULL) return -4;
const uint8_t *ptr = sector0 + 8; /* skip region_count (4) + unknown (4) */
for(uint32_t i = 0; i < region_count; i++)
{
r[i].start_sector = read_be32(ptr);
r[i].end_sector = read_be32(ptr + 4);
ptr += 8;
if(r[i].start_sector > r[i].end_sector)
{
free(r);
return -5;
}
}
*regions = r;
*count = region_count;
return 0;
}
int32_t ps3_serialize_encryption_map(const Ps3PlaintextRegion *regions, uint32_t count, uint8_t **out_data,
uint32_t *out_length)
{
if(out_data == NULL || out_length == NULL) return -1;
if(count > PS3_MAX_PLAINTEXT_REGIONS) return -3;
uint32_t size = 4 + count * 8;
uint8_t *buf = (uint8_t *)malloc(size);
if(buf == NULL) return -4;
write_le32(buf, count);
for(uint32_t i = 0; i < count; i++)
{
write_le32(buf + 4 + i * 8, regions[i].start_sector);
write_le32(buf + 4 + i * 8 + 4, regions[i].end_sector);
}
*out_data = buf;
*out_length = size;
return 0;
}
int32_t ps3_deserialize_encryption_map(const uint8_t *data, uint32_t length, Ps3PlaintextRegion **regions,
uint32_t *count)
{
if(data == NULL || regions == NULL || count == NULL) return -1;
if(length < 4) return -2;
uint32_t region_count = read_le32(data);
if(region_count > PS3_MAX_PLAINTEXT_REGIONS) return -3;
if(region_count == 0)
{
*regions = NULL;
*count = 0;
return 0;
}
uint32_t required = 4 + region_count * 8;
if(length < required) return -2;
Ps3PlaintextRegion *r = (Ps3PlaintextRegion *)malloc(region_count * sizeof(Ps3PlaintextRegion));
if(r == NULL) return -4;
for(uint32_t i = 0; i < region_count; i++)
{
r[i].start_sector = read_le32(data + 4 + i * 8);
r[i].end_sector = read_le32(data + 4 + i * 8 + 4);
}
*regions = r;
*count = region_count;
return 0;
}
bool ps3_is_sector_encrypted(const Ps3PlaintextRegion *plaintext_regions, uint32_t region_count,
uint64_t sector_address)
{
if(plaintext_regions == NULL || region_count == 0) return true;
for(uint32_t i = 0; i < region_count; i++)
if(sector_address >= plaintext_regions[i].start_sector && sector_address <= plaintext_regions[i].end_sector)
return false;
return true;
}

View File

@@ -0,0 +1,94 @@
/*
* This file is part of the Aaru Data Preservation Suite.
* Copyright (c) 2019-2026 Natalia Portillo.
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see
* <https://www.gnu.org/licenses/>.
*
* PS3 encryption map: plaintext region parsing, serialization, and lookup.
*/
#ifndef LIBAARUFORMAT_PS3_ENCRYPTION_MAP_H
#define LIBAARUFORMAT_PS3_ENCRYPTION_MAP_H
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C"
{
#endif
/** A plaintext (unencrypted) region on a PS3 disc. */
typedef struct Ps3PlaintextRegion
{
uint32_t start_sector; /**< First sector of plaintext region (inclusive). */
uint32_t end_sector; /**< Last sector of plaintext region (inclusive). */
} Ps3PlaintextRegion;
#define PS3_MAX_PLAINTEXT_REGIONS 32
/**
* @brief Parse the encryption map from PS3 disc sector 0 (big-endian on disc).
*
* @param sector0 Pointer to the 2048-byte sector 0 data.
* @param length Length of sector0 data (must be >= 264).
* @param regions Output: allocated array of plaintext regions. Caller must free().
* @param count Output: number of regions.
* @return 0 on success, negative error code on failure.
*/
int32_t ps3_parse_encryption_map(const uint8_t *sector0, uint32_t length, Ps3PlaintextRegion **regions,
uint32_t *count);
/**
* @brief Serialize plaintext regions to little-endian binary for storage as a media tag.
*
* Format: [4B region_count LE] + region_count × [4B start LE, 4B end LE]
*
* @param regions Array of plaintext regions.
* @param count Number of regions.
* @param out_data Output: allocated buffer. Caller must free().
* @param out_length Output: size of the buffer in bytes.
* @return 0 on success, negative error code on failure.
*/
int32_t ps3_serialize_encryption_map(const Ps3PlaintextRegion *regions, uint32_t count, uint8_t **out_data,
uint32_t *out_length);
/**
* @brief Deserialize plaintext regions from little-endian binary (media tag format).
*
* @param data Serialized data buffer.
* @param length Length of data buffer.
* @param regions Output: allocated array of plaintext regions. Caller must free().
* @param count Output: number of regions.
* @return 0 on success, negative error code on failure.
*/
int32_t ps3_deserialize_encryption_map(const uint8_t *data, uint32_t length, Ps3PlaintextRegion **regions,
uint32_t *count);
/**
* @brief Check whether a sector is encrypted (i.e., not in any plaintext region).
*
* @param plaintext_regions Array of plaintext regions.
* @param region_count Number of regions.
* @param sector_address Sector number to check.
* @return true if the sector is encrypted (not in any plaintext region).
*/
bool ps3_is_sector_encrypted(const Ps3PlaintextRegion *plaintext_regions, uint32_t region_count,
uint64_t sector_address);
#ifdef __cplusplus
}
#endif
#endif /* LIBAARUFORMAT_PS3_ENCRYPTION_MAP_H */

View File

@@ -88,6 +88,8 @@ add_executable(tests_run
open_image.cpp
create_image.cpp
ps3_aes.cpp
ps3_crypto.cpp
ps3_encryption_map.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../src/ps3/aes128.c
${CMAKE_CURRENT_SOURCE_DIR}/../src/ps3/ps3_crypto.c
${CMAKE_CURRENT_SOURCE_DIR}/../src/ps3/ps3_encryption_map.c

162
tests/ps3_crypto.cpp Normal file
View File

@@ -0,0 +1,162 @@
/*
* This file is part of the Aaru Data Preservation Suite.
* Copyright (c) 2019-2026 Natalia Portillo.
*
* PS3 key derivation, IV derivation, and sector encrypt/decrypt tests.
*/
#include <cstdint>
#include <cstring>
#include "gtest/gtest.h"
extern "C"
{
#include "../src/ps3/ps3_crypto.h"
}
/* Test: IV derivation for sector 0 */
TEST(PS3Crypto, IvDeriveSector0)
{
uint8_t iv[16];
uint8_t expected[16] = {0};
ps3_derive_iv(0, iv);
EXPECT_EQ(0, memcmp(iv, expected, 16));
}
/* Test: IV derivation for sector 1 */
TEST(PS3Crypto, IvDeriveSector1)
{
uint8_t iv[16];
uint8_t expected[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
ps3_derive_iv(1, iv);
EXPECT_EQ(0, memcmp(iv, expected, 16));
}
/* Test: IV derivation for sector 0xFF */
TEST(PS3Crypto, IvDeriveSectorFF)
{
uint8_t iv[16];
uint8_t expected[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF};
ps3_derive_iv(0xFF, iv);
EXPECT_EQ(0, memcmp(iv, expected, 16));
}
/* Test: IV derivation for sector 0x12345678 */
TEST(PS3Crypto, IvDeriveSectorMultiByte)
{
uint8_t iv[16];
uint8_t expected[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x12, 0x34, 0x56, 0x78};
ps3_derive_iv(0x12345678, iv);
EXPECT_EQ(0, memcmp(iv, expected, 16));
}
/* Test: IV derivation for large sector number spanning 8 bytes */
TEST(PS3Crypto, IvDeriveSectorLarge)
{
uint8_t iv[16];
uint8_t expected[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89};
ps3_derive_iv(0xABCDEF0123456789ULL, iv);
EXPECT_EQ(0, memcmp(iv, expected, 16));
}
/* Test: disc key derivation produces consistent output */
TEST(PS3Crypto, DiscKeyDerivation)
{
uint8_t data1[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
uint8_t disc_key[16] = {0};
ps3_derive_disc_key(data1, disc_key);
/* Disc key should not be all zeros (derivation produced output) */
uint8_t zeros[16] = {0};
EXPECT_NE(0, memcmp(disc_key, zeros, 16));
/* Disc key should not equal data1 (it was actually transformed) */
EXPECT_NE(0, memcmp(disc_key, data1, 16));
/* Derivation should be deterministic */
uint8_t disc_key2[16] = {0};
ps3_derive_disc_key(data1, disc_key2);
EXPECT_EQ(0, memcmp(disc_key, disc_key2, 16));
}
/* Test: different data1 values produce different disc keys */
TEST(PS3Crypto, DiscKeyDifferentData1)
{
uint8_t data1_a[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
uint8_t data1_b[16] = {0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8,
0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0};
uint8_t disc_key_a[16], disc_key_b[16];
ps3_derive_disc_key(data1_a, disc_key_a);
ps3_derive_disc_key(data1_b, disc_key_b);
EXPECT_NE(0, memcmp(disc_key_a, disc_key_b, 16));
}
/* Test: sector encrypt/decrypt round-trip */
TEST(PS3Crypto, SectorRoundTrip)
{
uint8_t disc_key[16] = {0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE,
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
uint8_t original[2048];
uint8_t data[2048];
for(int i = 0; i < 2048; i++) original[i] = (uint8_t)(i & 0xFF);
memcpy(data, original, 2048);
ps3_encrypt_sector(disc_key, 42, data, 2048);
EXPECT_NE(0, memcmp(data, original, 2048));
ps3_decrypt_sector(disc_key, 42, data, 2048);
EXPECT_EQ(0, memcmp(data, original, 2048));
}
/* Test: different sectors produce different ciphertext */
TEST(PS3Crypto, DifferentSectorsDifferentCiphertext)
{
uint8_t disc_key[16] = {0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE,
0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
uint8_t plaintext[2048];
for(int i = 0; i < 2048; i++) plaintext[i] = (uint8_t)(i & 0xFF);
uint8_t data_a[2048], data_b[2048];
memcpy(data_a, plaintext, 2048);
memcpy(data_b, plaintext, 2048);
ps3_encrypt_sector(disc_key, 100, data_a, 2048);
ps3_encrypt_sector(disc_key, 200, data_b, 2048);
/* Same plaintext encrypted with different sector IVs should differ */
EXPECT_NE(0, memcmp(data_a, data_b, 2048));
}
/* Test: full key derivation + sector encrypt/decrypt */
TEST(PS3Crypto, FullPipelineRoundTrip)
{
uint8_t data1[16] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00};
uint8_t disc_key[16];
ps3_derive_disc_key(data1, disc_key);
uint8_t original[2048];
uint8_t data[2048];
for(int i = 0; i < 2048; i++) original[i] = (uint8_t)((i * 3 + 7) & 0xFF);
memcpy(data, original, 2048);
ps3_encrypt_sector(disc_key, 1000, data, 2048);
ps3_decrypt_sector(disc_key, 1000, data, 2048);
EXPECT_EQ(0, memcmp(data, original, 2048));
}

View File

@@ -0,0 +1,264 @@
/*
* This file is part of the Aaru Data Preservation Suite.
* Copyright (c) 2019-2026 Natalia Portillo.
*
* PS3 encryption map parsing, serialization, and sector lookup tests.
*/
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include "gtest/gtest.h"
extern "C"
{
#include "../src/ps3/ps3_encryption_map.h"
}
/*
* Build a synthetic sector 0 in big-endian format:
* offset 0x00: region_count = 2 (BE)
* offset 0x04: unknown = 0 (BE)
* offset 0x08: region[0].start = 0 (BE)
* offset 0x0C: region[0].end = 31 (BE)
* offset 0x10: region[1].start = 256 (BE)
* offset 0x14: region[1].end = 511 (BE)
*/
static void build_synthetic_sector0(uint8_t *buf, uint32_t size)
{
memset(buf, 0, size);
/* region_count = 2, big-endian */
buf[0] = 0;
buf[1] = 0;
buf[2] = 0;
buf[3] = 2;
/* unknown field */
buf[4] = 0;
buf[5] = 0;
buf[6] = 0;
buf[7] = 0;
/* region[0]: start=0, end=31 */
buf[8] = 0;
buf[9] = 0;
buf[10] = 0;
buf[11] = 0; /* start=0 */
buf[12] = 0;
buf[13] = 0;
buf[14] = 0;
buf[15] = 31; /* end=31 */
/* region[1]: start=256, end=511 */
buf[16] = 0;
buf[17] = 0;
buf[18] = 1;
buf[19] = 0; /* start=256 */
buf[20] = 0;
buf[21] = 0;
buf[22] = 1;
buf[23] = 0xFF; /* end=511 */
}
/* Test: parse valid sector 0 */
TEST(PS3EncMap, ParseValid)
{
uint8_t sector0[2048];
build_synthetic_sector0(sector0, 2048);
Ps3PlaintextRegion *regions = NULL;
uint32_t count = 0;
int32_t ret = ps3_parse_encryption_map(sector0, 2048, &regions, &count);
ASSERT_EQ(0, ret);
ASSERT_EQ(2u, count);
ASSERT_NE(nullptr, regions);
EXPECT_EQ(0u, regions[0].start_sector);
EXPECT_EQ(31u, regions[0].end_sector);
EXPECT_EQ(256u, regions[1].start_sector);
EXPECT_EQ(511u, regions[1].end_sector);
free(regions);
}
/* Test: parse zero regions */
TEST(PS3EncMap, ParseZeroRegions)
{
uint8_t sector0[2048];
memset(sector0, 0, 2048); /* region_count = 0 */
Ps3PlaintextRegion *regions = NULL;
uint32_t count = 99;
int32_t ret = ps3_parse_encryption_map(sector0, 2048, &regions, &count);
ASSERT_EQ(0, ret);
EXPECT_EQ(0u, count);
EXPECT_EQ(nullptr, regions);
}
/* Test: parse rejects too many regions */
TEST(PS3EncMap, ParseTooManyRegions)
{
uint8_t sector0[2048];
memset(sector0, 0, 2048);
/* region_count = 33 (> max 32) */
sector0[3] = 33;
Ps3PlaintextRegion *regions = NULL;
uint32_t count = 0;
int32_t ret = ps3_parse_encryption_map(sector0, 2048, &regions, &count);
EXPECT_LT(ret, 0);
}
/* Test: parse rejects invalid region (start > end) */
TEST(PS3EncMap, ParseInvalidRegion)
{
uint8_t sector0[2048];
memset(sector0, 0, 2048);
sector0[3] = 1; /* 1 region */
/* start=100, end=50 (invalid) */
sector0[11] = 100;
sector0[15] = 50;
Ps3PlaintextRegion *regions = NULL;
uint32_t count = 0;
int32_t ret = ps3_parse_encryption_map(sector0, 2048, &regions, &count);
EXPECT_LT(ret, 0);
}
/* Test: parse rejects too-short buffer */
TEST(PS3EncMap, ParseBufferTooShort)
{
uint8_t sector0[8];
memset(sector0, 0, 8);
sector0[3] = 1; /* 1 region, but buffer is only 8 bytes (need 16) */
Ps3PlaintextRegion *regions = NULL;
uint32_t count = 0;
int32_t ret = ps3_parse_encryption_map(sector0, 8, &regions, &count);
EXPECT_LT(ret, 0);
}
/* Test: serialize and deserialize round-trip */
TEST(PS3EncMap, SerializeDeserializeRoundTrip)
{
Ps3PlaintextRegion input[3] = {{0, 31}, {256, 511}, {1024, 2047}};
uint8_t *data = NULL;
uint32_t length = 0;
int32_t ret = ps3_serialize_encryption_map(input, 3, &data, &length);
ASSERT_EQ(0, ret);
ASSERT_NE(nullptr, data);
ASSERT_EQ(4u + 3u * 8u, length); /* 4 + 24 = 28 */
Ps3PlaintextRegion *output = NULL;
uint32_t count = 0;
ret = ps3_deserialize_encryption_map(data, length, &output, &count);
ASSERT_EQ(0, ret);
ASSERT_EQ(3u, count);
ASSERT_NE(nullptr, output);
for(uint32_t i = 0; i < 3; i++)
{
EXPECT_EQ(input[i].start_sector, output[i].start_sector);
EXPECT_EQ(input[i].end_sector, output[i].end_sector);
}
free(data);
free(output);
}
/* Test: serialize zero regions */
TEST(PS3EncMap, SerializeZeroRegions)
{
uint8_t *data = NULL;
uint32_t length = 0;
int32_t ret = ps3_serialize_encryption_map(NULL, 0, &data, &length);
ASSERT_EQ(0, ret);
ASSERT_NE(nullptr, data);
ASSERT_EQ(4u, length);
/* First 4 bytes should be 0 (LE) */
EXPECT_EQ(0, data[0]);
EXPECT_EQ(0, data[1]);
EXPECT_EQ(0, data[2]);
EXPECT_EQ(0, data[3]);
free(data);
}
/* Test: sector encrypted — outside plaintext regions */
TEST(PS3EncMap, SectorEncryptedOutside)
{
Ps3PlaintextRegion regions[2] = {{0, 31}, {256, 511}};
/* Sector 32: between regions → encrypted */
EXPECT_TRUE(ps3_is_sector_encrypted(regions, 2, 32));
/* Sector 100: between regions → encrypted */
EXPECT_TRUE(ps3_is_sector_encrypted(regions, 2, 100));
/* Sector 255: just before second region → encrypted */
EXPECT_TRUE(ps3_is_sector_encrypted(regions, 2, 255));
/* Sector 512: just after second region → encrypted */
EXPECT_TRUE(ps3_is_sector_encrypted(regions, 2, 512));
/* Sector 10000: way past all regions → encrypted */
EXPECT_TRUE(ps3_is_sector_encrypted(regions, 2, 10000));
}
/* Test: sector not encrypted — inside plaintext regions */
TEST(PS3EncMap, SectorPlaintextInside)
{
Ps3PlaintextRegion regions[2] = {{0, 31}, {256, 511}};
/* Sector 0: first sector of first region → plaintext */
EXPECT_FALSE(ps3_is_sector_encrypted(regions, 2, 0));
/* Sector 15: middle of first region → plaintext */
EXPECT_FALSE(ps3_is_sector_encrypted(regions, 2, 15));
/* Sector 31: last sector of first region → plaintext */
EXPECT_FALSE(ps3_is_sector_encrypted(regions, 2, 31));
/* Sector 256: first sector of second region → plaintext */
EXPECT_FALSE(ps3_is_sector_encrypted(regions, 2, 256));
/* Sector 400: middle of second region → plaintext */
EXPECT_FALSE(ps3_is_sector_encrypted(regions, 2, 400));
/* Sector 511: last sector of second region → plaintext */
EXPECT_FALSE(ps3_is_sector_encrypted(regions, 2, 511));
}
/* Test: sector encrypted when no regions defined */
TEST(PS3EncMap, SectorEncryptedNoRegions)
{
EXPECT_TRUE(ps3_is_sector_encrypted(NULL, 0, 0));
EXPECT_TRUE(ps3_is_sector_encrypted(NULL, 0, 100));
}
/* Test: parse then use for sector lookup */
TEST(PS3EncMap, ParseThenLookup)
{
uint8_t sector0[2048];
build_synthetic_sector0(sector0, 2048);
Ps3PlaintextRegion *regions = NULL;
uint32_t count = 0;
int32_t ret = ps3_parse_encryption_map(sector0, 2048, &regions, &count);
ASSERT_EQ(0, ret);
/* Sector 0: in first plaintext region */
EXPECT_FALSE(ps3_is_sector_encrypted(regions, count, 0));
/* Sector 50: between regions → encrypted */
EXPECT_TRUE(ps3_is_sector_encrypted(regions, count, 50));
/* Sector 300: in second plaintext region */
EXPECT_FALSE(ps3_is_sector_encrypted(regions, count, 300));
/* Sector 600: past all regions → encrypted */
EXPECT_TRUE(ps3_is_sector_encrypted(regions, count, 600));
free(regions);
}