diff --git a/include/aaruformat/context.h b/include/aaruformat/context.h index db099bd..fd53e50 100644 --- a/include/aaruformat/context.h +++ b/include/aaruformat/context.h @@ -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 diff --git a/include/aaruformat/errors.h b/include/aaruformat/errors.h index 7fb80dc..cef2306 100644 --- a/include/aaruformat/errors.h +++ b/include/aaruformat/errors.h @@ -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: diff --git a/src/close.c b/src/close.c index b0ad207..b14f855 100644 --- a/src/close.c +++ b/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); diff --git a/src/ps3/ps3_crypto.c b/src/ps3/ps3_crypto.c index 2c2e966..bdeccba 100644 --- a/src/ps3/ps3_crypto.c +++ b/src/ps3/ps3_crypto.c @@ -19,10 +19,14 @@ */ #include +#include #include +#include + #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; + } + } + } +} diff --git a/src/ps3/ps3_crypto.h b/src/ps3/ps3_crypto.h index 8fd0317..1e5ce7d 100644 --- a/src/ps3/ps3_crypto.h +++ b/src/ps3/ps3_crypto.h @@ -24,50 +24,64 @@ #include #include +/* 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 } diff --git a/src/read.c b/src/read.c index c04b0af..53e5dde 100644 --- a/src/read.c +++ b/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; } diff --git a/src/write.c b/src/write.c index 806b490..e45608a 100644 --- a/src/write.c +++ b/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; }