diff --git a/doc/general.texi b/doc/general.texi index dc22d90257..617be66dcc 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -226,6 +226,8 @@ library: @item 4xm @tab @tab X @tab 4X Technologies format, used in some games. @item 8088flex TMV @tab @tab X +@item AAX @tab @tab X + @tab Audible Enhanced Audio format, used in audiobooks. @item ACT Voice @tab @tab X @tab contains G.729 audio @item Adobe Filmstrip @tab X @tab X diff --git a/doc/muxers.texi b/doc/muxers.texi index 8e3eb4ba7e..ccd222c916 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -667,6 +667,13 @@ point on IIS with this muxer. Example: ffmpeg -re @var{} -movflags isml+frag_keyframe -f ismv http://server/publishingpoint.isml/Streams(Encoder1) @end example +@subsection Audible AAX + +Audible AAX files are encrypted M4B files, and they can be decrypted by specifying a 4 byte activation secret. +@example +ffmpeg -activation_bytes 1CEB00DA -i test.aax -vn -c:a copy output.mp4 +@end example + @section mp3 The MP3 muxer writes a raw MP3 stream with the following optional features: diff --git a/libavformat/isom.h b/libavformat/isom.h index 5d48989fd9..aee9d6e8c7 100644 --- a/libavformat/isom.h +++ b/libavformat/isom.h @@ -198,6 +198,14 @@ typedef struct MOVContext { MOVFragmentIndex** fragment_index_data; unsigned fragment_index_count; int atom_depth; + unsigned int aax_mode; ///< 'aax' file has been detected + uint8_t file_key[20]; + uint8_t file_iv[20]; + void *activation_bytes; + int activation_bytes_size; + void *audible_fixed_key; + int audible_fixed_key_size; + struct AVAES *aes_decrypt; } MOVContext; int ff_mp4_read_descr_len(AVIOContext *pb); diff --git a/libavformat/mov.c b/libavformat/mov.c index 9d6b2e4f21..92bf2f8940 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -37,6 +37,8 @@ #include "libavutil/dict.h" #include "libavutil/display.h" #include "libavutil/opt.h" +#include "libavutil/aes.h" +#include "libavutil/sha.h" #include "libavutil/timecode.h" #include "libavcodec/ac3tab.h" #include "avformat.h" @@ -807,6 +809,120 @@ static int mov_read_mdat(MOVContext *c, AVIOContext *pb, MOVAtom atom) return 0; /* now go for moov */ } +#define DRM_BLOB_SIZE 56 + +static int mov_read_adrm(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + uint8_t intermediate_key[20]; + uint8_t intermediate_iv[20]; + uint8_t input[64]; + uint8_t output[64]; + uint8_t file_checksum[20]; + uint8_t calculated_checksum[20]; + struct AVSHA *sha; + int i; + int ret = 0; + uint8_t *activation_bytes = c->activation_bytes; + uint8_t *fixed_key = c->audible_fixed_key; + + c->aax_mode = 1; + + sha = av_sha_alloc(); + if (!sha) + return AVERROR(ENOMEM); + c->aes_decrypt = av_aes_alloc(); + if (!c->aes_decrypt) { + ret = AVERROR(ENOMEM); + goto fail; + } + + /* drm blob processing */ + avio_read(pb, output, 8); // go to offset 8, absolute postion 0x251 + avio_read(pb, input, DRM_BLOB_SIZE); + avio_read(pb, output, 4); // go to offset 4, absolute postion 0x28d + avio_read(pb, file_checksum, 20); + + av_log(c->fc, AV_LOG_INFO, "[aax] file checksum == "); // required by external tools + for (i = 0; i < 20; i++) + av_log(sha, AV_LOG_INFO, "%02x", file_checksum[i]); + av_log(c->fc, AV_LOG_INFO, "\n"); + + /* verify activation data */ + if (!activation_bytes || c->activation_bytes_size != 4) { + av_log(c->fc, AV_LOG_FATAL, "[aax] activation_bytes option is missing!\n"); + ret = AVERROR(EINVAL); + goto fail; + } + if (c->activation_bytes_size != 4) { + av_log(c->fc, AV_LOG_FATAL, "[aax] activation_bytes value needs to be 4 bytes!\n"); + ret = AVERROR(EINVAL); + goto fail; + } + + /* verify fixed key */ + if (c->audible_fixed_key_size != 16) { + av_log(c->fc, AV_LOG_FATAL, "[aax] audible_fixed_key value needs to be 16 bytes!\n"); + ret = AVERROR(EINVAL); + goto fail; + } + + /* AAX (and AAX+) key derivation */ + av_sha_init(sha, 160); + av_sha_update(sha, fixed_key, 16); + av_sha_update(sha, activation_bytes, 4); + av_sha_final(sha, intermediate_key); + av_sha_init(sha, 160); + av_sha_update(sha, fixed_key, 16); + av_sha_update(sha, intermediate_key, 20); + av_sha_update(sha, activation_bytes, 4); + av_sha_final(sha, intermediate_iv); + av_sha_init(sha, 160); + av_sha_update(sha, intermediate_key, 16); + av_sha_update(sha, intermediate_iv, 16); + av_sha_final(sha, calculated_checksum); + if (memcmp(calculated_checksum, file_checksum, 20)) { // critical error + av_log(c->fc, AV_LOG_ERROR, "[aax] mismatch in checksums!\n"); + ret = AVERROR_INVALIDDATA; + goto fail; + } + av_aes_init(c->aes_decrypt, intermediate_key, 128, 1); + av_aes_crypt(c->aes_decrypt, output, input, DRM_BLOB_SIZE >> 4, intermediate_iv, 1); + for (i = 0; i < 4; i++) { + // file data (in output) is stored in big-endian mode + if (activation_bytes[i] != output[3 - i]) { // critical error + av_log(c->fc, AV_LOG_ERROR, "[aax] error in drm blob decryption!\n"); + ret = AVERROR_INVALIDDATA; + goto fail; + } + } + memcpy(c->file_key, output + 8, 16); + memcpy(input, output + 26, 16); + av_sha_init(sha, 160); + av_sha_update(sha, input, 16); + av_sha_update(sha, c->file_key, 16); + av_sha_update(sha, fixed_key, 16); + av_sha_final(sha, c->file_iv); + +fail: + av_free(sha); + + return ret; +} + +// Audible AAX (and AAX+) bytestream decryption +static int aax_filter(uint8_t *input, int size, MOVContext *c) +{ + int blocks = 0; + unsigned char iv[16]; + + memcpy(iv, c->file_iv, 16); // iv is overwritten + blocks = size >> 4; // trailing bytes are not encrypted! + av_aes_init(c->aes_decrypt, c->file_key, 128, 1); + av_aes_crypt(c->aes_decrypt, input, input, blocks, iv, 1); + + return 0; +} + /* read major brand, minor version and compatible brands and store them as metadata */ static int mov_read_ftyp(MOVContext *c, AVIOContext *pb, MOVAtom atom) { @@ -3637,6 +3753,7 @@ static const MOVParseTableEntry mov_default_parse_table[] = { { MKTAG('e','l','s','t'), mov_read_elst }, { MKTAG('e','n','d','a'), mov_read_enda }, { MKTAG('f','i','e','l'), mov_read_fiel }, +{ MKTAG('a','d','r','m'), mov_read_adrm }, { MKTAG('f','t','y','p'), mov_read_ftyp }, { MKTAG('g','l','b','l'), mov_read_glbl }, { MKTAG('h','d','l','r'), mov_read_hdlr }, @@ -4058,6 +4175,8 @@ static int mov_read_close(AVFormatContext *s) } av_freep(&mov->fragment_index_data); + av_freep(&mov->aes_decrypt); + return 0; } @@ -4477,6 +4596,9 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) pkt->flags |= sample->flags & AVINDEX_KEYFRAME ? AV_PKT_FLAG_KEY : 0; pkt->pos = sample->pos; + if (mov->aax_mode) + aax_filter(pkt->data, pkt->size, mov); + return 0; } @@ -4590,6 +4712,12 @@ static const AVOption mov_options[] = { AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = FLAGS }, { "export_xmp", "Export full XMP metadata", OFFSET(export_xmp), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = FLAGS }, + { "activation_bytes", "Secret bytes for Audible AAX files", OFFSET(activation_bytes), + AV_OPT_TYPE_BINARY, .flags = AV_OPT_FLAG_DECODING_PARAM }, + { "audible_fixed_key", // extracted from libAAX_SDK.so and AAXSDKWin.dll files! + "Fixed key used for handling Audible AAX files", OFFSET(audible_fixed_key), + AV_OPT_TYPE_BINARY, {.str="77214d4b196a87cd520045fd20a51d67"}, + .flags = AV_OPT_FLAG_DECODING_PARAM }, { NULL }, };