Implement PS3 encryption support with lazy initialization and error handling

This commit is contained in:
2026-03-15 19:12:59 +00:00
parent 85dc9ea1b8
commit 73dac131b8
7 changed files with 221 additions and 45 deletions

View File

@@ -21,7 +21,6 @@
#include "blake3.h"
#include "crc64.h"
#include "structs/flux.h"
#include "hash_map.h"
#include "lru.h"
#include "md5.h"
@@ -29,6 +28,7 @@
#include "sha256.h"
#include "spamsum.h"
#include "structs.h"
#include "structs/flux.h"
#include "utarray.h"
typedef struct FluxCaptureMapEntry FluxCaptureMapEntry;
@@ -308,10 +308,10 @@ typedef struct aaruformat_context
bool is_tape; ///< True if the image is a tape image
/* Flux data structures */
FluxHeader flux_data_header; ///< Flux data header (if present).
FluxEntry *flux_entries; ///< Array of flux entries (flux_data_header.entries elements).
UT_array *flux_captures; ///< Pending flux capture payloads (write path).
FluxCaptureMapEntry *flux_map; ///< Hash map for flux capture lookup by head/track/subtrack/capture index.
FluxHeader flux_data_header; ///< Flux data header (if present).
FluxEntry *flux_entries; ///< Array of flux entries (flux_data_header.entries elements).
UT_array *flux_captures; ///< Pending flux capture payloads (write path).
FluxCaptureMapEntry *flux_map; ///< Hash map for flux capture lookup by head/track/subtrack/capture index.
/* Dirty flags (controls write behavior in close.c) */
bool dirty_secondary_ddt; ///< True if secondary DDT tables should be written during close
@@ -338,6 +338,12 @@ typedef struct aaruformat_context
bool dirty_json_block; ///< True if JSON metadata block should be written during close
bool dirty_flux_block; ///< True if flux block should be written during close
bool dirty_index_block; ///< True if index block should be written during close
// PS3 encryption support (lazy-initialized on first use)
uint8_t *ps3_disc_key; ///< Cached disc key (16 bytes), NULL if not loaded
void *ps3_plaintext_regions; ///< Parsed Ps3PlaintextRegion array (max 32), NULL if not loaded
uint32_t ps3_plaintext_region_count; ///< Number of plaintext regions
bool ps3_encryption_initialized; ///< Whether lazy init has occurred
} aaruformat_context;
/** \struct DumpHardwareEntriesWithData

View File

@@ -70,6 +70,9 @@
#define AARUF_ERROR_INVALID_SECTOR_LENGTH (-31) ///< Sector length is too big.
#define AARUF_ERROR_FLUX_DATA_NOT_FOUND (-32) ///< Requested flux data not present in image.
#define AARUF_ERROR_INCOMPATIBLE_FEATURES (-33) ///< Image requires features not supported by this library.
#define AARUF_ERROR_CANNOT_ENCRYPT_SECTOR (-34) ///< AES sector encryption failed.
#define AARUF_ERROR_CANNOT_DECRYPT_SECTOR (-35) ///< AES sector decryption failed.
#define AARUF_ERROR_MISSING_ENCRYPTION_KEY (-36) ///< Required encryption key not present in media tags.
/** @} */
/** \name Non-fatal sector status codes (non-negative)
@@ -147,6 +150,12 @@ static inline const char *aaruformat_error_string(int code)
return "Cannot set DDT entry";
case AARUF_ERROR_INCOMPATIBLE_FEATURES:
return "Image requires unsupported features";
case AARUF_ERROR_CANNOT_ENCRYPT_SECTOR:
return "Cannot encrypt sector";
case AARUF_ERROR_CANNOT_DECRYPT_SECTOR:
return "Cannot decrypt sector";
case AARUF_ERROR_MISSING_ENCRYPTION_KEY:
return "Missing encryption key";
/* Status */
case AARUF_STATUS_OK:

View File

@@ -5281,6 +5281,18 @@ AARU_EXPORT int AARU_CALL aaruf_close(void *context)
free(ctx->checksums.spamsum);
ctx->checksums.spamsum = NULL;
// Free PS3 encryption context
if(ctx->ps3_disc_key != NULL)
{
memset(ctx->ps3_disc_key, 0, 16);
free(ctx->ps3_disc_key);
ctx->ps3_disc_key = NULL;
}
free(ctx->ps3_plaintext_regions);
ctx->ps3_plaintext_regions = NULL;
ctx->ps3_plaintext_region_count = 0;
ctx->ps3_encryption_initialized = false;
free(ctx->sector_id);
free(ctx->sector_ied);
free(ctx->sector_cpr_mai);

View File

@@ -19,10 +19,14 @@
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <aaruformat.h>
#include "aes128.h"
#include "ps3_crypto.h"
#include "ps3_encryption_map.h"
/* PS3 Encryption Round Key — publicly known constant */
static const uint8_t PS3_ERK[16] = {0x38, 0x0B, 0xCF, 0x0B, 0x53, 0x45, 0x5B, 0x3C,
@@ -62,3 +66,56 @@ void ps3_decrypt_sector(const uint8_t disc_key[16], uint64_t sector_num, uint8_t
ps3_derive_iv(sector_num, iv);
aes128_cbc_decrypt(disc_key, iv, data, length);
}
void ps3_lazy_init(aaruformat_context *ctx)
{
if(ctx == NULL) return;
/* Read disc key from media tags */
if(ctx->ps3_disc_key == NULL)
{
mediaTagEntry *item = NULL;
int32_t tag = kMediaTagPs3DiscKey;
HASH_FIND_INT(ctx->mediaTags, &tag, item);
if(item != NULL && item->length == 16)
{
ctx->ps3_disc_key = (uint8_t *)malloc(16);
if(ctx->ps3_disc_key != NULL) memcpy(ctx->ps3_disc_key, item->data, 16);
}
else
{
/* Try deriving from data1 */
tag = kMediaTagPs3Data1;
HASH_FIND_INT(ctx->mediaTags, &tag, item);
if(item != NULL && item->length == 16)
{
ctx->ps3_disc_key = (uint8_t *)malloc(16);
if(ctx->ps3_disc_key != NULL) ps3_derive_disc_key(item->data, ctx->ps3_disc_key);
}
}
}
/* Read and deserialize encryption map from media tags */
if(ctx->ps3_plaintext_regions == NULL)
{
mediaTagEntry *item = NULL;
int32_t tag = kMediaTagPs3EncryptionMap;
HASH_FIND_INT(ctx->mediaTags, &tag, item);
if(item != NULL && item->length >= 4)
{
Ps3PlaintextRegion *regions = NULL;
uint32_t count = 0;
if(ps3_deserialize_encryption_map(item->data, item->length, &regions, &count) == 0)
{
ctx->ps3_plaintext_regions = regions;
ctx->ps3_plaintext_region_count = count;
}
}
}
}

View File

@@ -24,50 +24,64 @@
#include <stdbool.h>
#include <stdint.h>
/* Forward declaration */
typedef struct aaruformat_context aaruformat_context;
#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 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 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 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);
/**
* @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);
/**
* @brief Lazy-initialize PS3 encryption state from context media tags.
*
* Reads kMediaTagPs3DiscKey and kMediaTagPs3EncryptionMap from the context's
* media tag hash table. Safe to call multiple times (no-op after first init).
* On failure, leaves fields NULL so callers degrade gracefully.
*
* @param ctx The aaruformat context.
*/
void ps3_lazy_init(aaruformat_context *ctx);
#ifdef __cplusplus
}

View File

@@ -23,6 +23,8 @@
#include "internal.h"
#include "log.h"
#include "ps3/ps3_crypto.h"
#include "ps3/ps3_encryption_map.h"
/**
* @brief Reads a media tag from the AaruFormat image.
@@ -411,6 +413,26 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t se
memcpy(data, block + offset * block_header->sectorSize, block_header->sectorSize);
*length = block_header->sectorSize;
// PS3 re-encryption: if stored decrypted, re-encrypt for caller
if(*sector_status == SectorStatusUnencrypted &&
(ctx->header.mediaType == PS3DVD || ctx->header.mediaType == PS3BD))
{
if(!ctx->ps3_encryption_initialized)
{
ps3_lazy_init(ctx);
ctx->ps3_encryption_initialized = true;
}
if(ctx->ps3_disc_key != NULL && ctx->ps3_plaintext_regions != NULL &&
ps3_is_sector_encrypted((const Ps3PlaintextRegion *)ctx->ps3_plaintext_regions,
ctx->ps3_plaintext_region_count, sector_address))
{
ps3_encrypt_sector(ctx->ps3_disc_key, sector_address, data, *length);
}
*sector_status = SectorStatusDumped;
}
TRACE("Exiting aaruf_read_sector() = AARUF_STATUS_OK");
return AARUF_STATUS_OK;
}
@@ -591,6 +613,25 @@ AARU_EXPORT int32_t AARU_CALL aaruf_read_sector(void *context, const uint64_t se
memcpy(data, block + offset * block_header->sectorSize, block_header->sectorSize);
*length = block_header->sectorSize;
// PS3 re-encryption: if stored decrypted, re-encrypt for caller
if(*sector_status == SectorStatusUnencrypted && (ctx->header.mediaType == PS3DVD || ctx->header.mediaType == PS3BD))
{
if(!ctx->ps3_encryption_initialized)
{
ps3_lazy_init(ctx);
ctx->ps3_encryption_initialized = true;
}
if(ctx->ps3_disc_key != NULL && ctx->ps3_plaintext_regions != NULL &&
ps3_is_sector_encrypted((const Ps3PlaintextRegion *)ctx->ps3_plaintext_regions,
ctx->ps3_plaintext_region_count, sector_address))
{
ps3_encrypt_sector(ctx->ps3_disc_key, sector_address, data, *length);
}
*sector_status = SectorStatusDumped;
}
TRACE("Exiting aaruf_read_sector() = AARUF_STATUS_OK");
return AARUF_STATUS_OK;
}

View File

@@ -26,6 +26,8 @@
#include "aaruformat.h"
#include "internal.h"
#include "log.h"
#include "ps3/ps3_crypto.h"
#include "ps3/ps3_encryption_map.h"
#include "structs/lisa_tag.h"
#include "xxhash.h"
@@ -206,6 +208,37 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector(void *context, uint64_t sector_
!negative && sector_address <= ctx->image_info.Sectors && !ctx->writing_long)
ctx->dirty_checksum_block = true;
// PS3 decryption: if caller sends encrypted data marked as "to be stored decrypted"
// Checksums have already been computed on the ciphertext above.
const uint8_t *write_data = data;
uint8_t *decrypted_buffer = NULL;
if(sector_status == SectorStatusUnencrypted && (ctx->header.mediaType == PS3DVD || ctx->header.mediaType == PS3BD))
{
if(!ctx->ps3_encryption_initialized)
{
ps3_lazy_init(ctx);
ctx->ps3_encryption_initialized = true;
}
if(ctx->ps3_disc_key != NULL && ctx->ps3_plaintext_regions != NULL &&
ps3_is_sector_encrypted((const Ps3PlaintextRegion *)ctx->ps3_plaintext_regions,
ctx->ps3_plaintext_region_count, sector_address))
{
decrypted_buffer = (uint8_t *)malloc(length);
if(decrypted_buffer == NULL)
{
FATAL("Could not allocate memory for PS3 decryption buffer");
return AARUF_ERROR_NOT_ENOUGH_MEMORY;
}
memcpy(decrypted_buffer, data, length);
ps3_decrypt_sector(ctx->ps3_disc_key, sector_address, decrypted_buffer, length);
write_data = decrypted_buffer;
}
}
// Close current block first
if(ctx->writing_buffer != NULL &&
// When sector size changes or block reaches maximum size
@@ -229,9 +262,9 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector(void *context, uint64_t sector_
if(ctx->deduplicate)
{
// Calculate 64-bit XXH3 hash of the sector
// Calculate 64-bit XXH3 hash of the sector (on decrypted data for dedup)
TRACE("Hashing sector data for deduplication");
uint64_t hash = XXH3_64bits(data, length);
uint64_t hash = XXH3_64bits(write_data, length);
// Check if the hash is already in the map
bool existing = lookup_map(ctx->sector_hash_map, hash, &ddt_entry);
@@ -248,6 +281,7 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector(void *context, uint64_t sector_
if(existing)
{
TRACE("Sector exists, so not writing to image");
free(decrypted_buffer);
TRACE("Exiting aaruf_write_sector() = AARUF_STATUS_OK");
return AARUF_STATUS_OK;
}
@@ -288,7 +322,8 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector(void *context, uint64_t sector_
{
ctx->current_track_type = track->type;
if(track->sequence == 0 && track->start == 0 && track->end == 0) ctx->current_track_type = kTrackTypeData;
if(track->sequence == 0 && track->start == 0 && track->end == 0)
ctx->current_track_type = kTrackTypeData;
}
else
ctx->current_track_type = kTrackTypeData;
@@ -336,12 +371,14 @@ AARU_EXPORT int32_t AARU_CALL aaruf_write_sector(void *context, uint64_t sector_
}
TRACE("Copying data to writing buffer at position %zu", ctx->writing_buffer_position);
memcpy(ctx->writing_buffer + ctx->writing_buffer_position, data, length);
memcpy(ctx->writing_buffer + ctx->writing_buffer_position, write_data, length);
TRACE("Advancing writing buffer position to %zu", ctx->writing_buffer_position + length);
ctx->writing_buffer_position += length;
TRACE("Advancing current block offset to %zu", ctx->current_block_offset + 1);
ctx->current_block_offset++;
free(decrypted_buffer);
TRACE("Exiting aaruf_write_sector() = AARUF_STATUS_OK");
return AARUF_STATUS_OK;
}