mirror of
https://github.com/aaru-dps/libaaruformat.git
synced 2026-09-16 11:54:18 +00:00
Added PS3 encryption.
This commit is contained in:
@@ -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
64
src/ps3/ps3_crypto.c
Normal 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
76
src/ps3/ps3_crypto.h
Normal 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 */
|
||||
161
src/ps3/ps3_encryption_map.c
Normal file
161
src/ps3/ps3_encryption_map.c
Normal 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;
|
||||
}
|
||||
94
src/ps3/ps3_encryption_map.h
Normal file
94
src/ps3/ps3_encryption_map.h
Normal 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 */
|
||||
@@ -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
162
tests/ps3_crypto.cpp
Normal 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));
|
||||
}
|
||||
264
tests/ps3_encryption_map.cpp
Normal file
264
tests/ps3_encryption_map.cpp
Normal 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, ®ions, &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, ®ions, &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, ®ions, &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, ®ions, &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, ®ions, &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, ®ions, &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);
|
||||
}
|
||||
Reference in New Issue
Block a user