mirror of
https://github.com/aaru-dps/libaaruformat.git
synced 2026-07-08 18:06:18 +00:00
Implement PS3 encryption support with lazy initialization and error handling
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
12
src/close.c
12
src/close.c
@@ -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);
|
||||
|
||||
@@ -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, ®ions, &count) == 0)
|
||||
{
|
||||
ctx->ps3_plaintext_regions = regions;
|
||||
ctx->ps3_plaintext_region_count = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
41
src/read.c
41
src/read.c
@@ -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;
|
||||
}
|
||||
|
||||
45
src/write.c
45
src/write.c
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user