From 52e1d041fa9181a3610734b99444899df2b49e49 Mon Sep 17 00:00:00 2001 From: Vesselin Bontchev <vesselin.bontc...@yandex.com> Date: Sat, 11 Jul 2015 18:02:47 +0000 Subject: [PATCH] Add support for Audible AAX (and AAX+) files
--- libavformat/mov.c | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) diff --git a/libavformat/mov.c b/libavformat/mov.c index 6d59863..3055de9 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -26,6 +26,7 @@ #include <inttypes.h> #include <limits.h> #include <stdint.h> +#include <ctype.h> #include "libavutil/attributes.h" #include "libavutil/channel_layout.h" @@ -37,6 +38,8 @@ #include "libavutil/dict.h" #include "libavutil/display.h" #include "libavutil/opt.h" +#include "libavutil/aes.h" +#include "libavutil/hash.h" #include "libavutil/timecode.h" #include "libavcodec/ac3tab.h" #include "avformat.h" @@ -807,6 +810,128 @@ static int mov_read_mdat(MOVContext *c, AVIOContext *pb, MOVAtom atom) return 0; /* now go for moov */ } +static unsigned int aax_mode = 0; +static unsigned char file_key[20]; +static unsigned char file_iv[20]; +static struct AVAES *aes_decrypt; + +static int hexchar2int(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +static void hex_encode(unsigned char *s, int len, unsigned char *o) +{ + char itoa16_private[16] = "0123456789abcdef"; + int i; + for (i = 0; i < len; ++i) { + o[0] = itoa16_private[s[i] >> 4]; + o[1] = itoa16_private[s[i] & 15]; + o += 2; + } +} + +#define DRM_BLOB_SIZE 56 + +// AAX (and AAX+) support is licensed under GPLv3 +static int aax_parser(MOVContext *c, AVIOContext *pb) +{ + unsigned char activation_bytes[4]; + + // extracted from libAAX_SDK.so and AAXSDKWin.dll files! + unsigned char fixed_key[] = { 0x77, 0x21, 0x4d, 0x4b, 0x19, 0x6a, 0x87, 0xcd, + 0x52, 0x00, 0x45, 0xfd, 0x20, 0xa5, 0x1d, 0x67 }; + unsigned char intermediate_key[20] = {0}; + unsigned char intermediate_iv[20] = {0}; + unsigned char input[4096] = {0}; + unsigned char output[4096] = {0}; + unsigned char file_checksum[20] = {0}; + unsigned char file_checksum_encoded[41] = {0}; + unsigned char file_key_encoded[41] = {0}; + unsigned char file_iv_encoded[41] = {0}; + unsigned char calculated_checksum[20]; + struct AVHashContext *ctx; + int a, b, i; + const char *magic = "drm"; + char *s; + + av_hash_alloc(&ctx, "SHA160"); + + aes_decrypt = av_aes_alloc(); + if (!aes_decrypt) { + return AVERROR(ENOMEM); + } + + /* extract activation data */ + s = getenv("activation_bytes"); + if (!s || strlen(s) < 8) { + av_log(c->fc, AV_LOG_ERROR, "[aax] export activation_bytes=<value> is missing!\n"); + exit(-1); + } + av_log(c->fc, AV_LOG_DEBUG, "[aax] activation_bytes == %s!\n", s); + for (i = 0; i < 4 && isxdigit(*s); i++) { + a = hexchar2int(*s++); + b = hexchar2int(*s++); + activation_bytes[i] = (a << 4) | b; + } + + /* drm blob processing */ + avio_seek(pb, 0x246, 0); + avio_read(pb, input, 3); + if (strncmp(input, magic, 3)) { + av_log(c->fc, AV_LOG_ERROR, "[aax] drm blob is missing from this file!\n"); + exit(-1); + } + avio_seek(pb, 0x251, 0); + avio_read(pb, input, DRM_BLOB_SIZE); + avio_seek(pb, 0x28d, 0); + avio_read(pb, file_checksum, 20); + hex_encode(file_checksum, 20, file_checksum_encoded); + av_log(c->fc, AV_LOG_DEBUG, "[aax] file checksum == %s\n", file_checksum_encoded); + + /* AAX (and AAX+) key derivation */ + av_hash_init(ctx); + av_hash_update(ctx, fixed_key, 16); + av_hash_update(ctx, activation_bytes, 4); + av_hash_final(ctx, intermediate_key); + av_hash_init(ctx); + av_hash_update(ctx, fixed_key, 16); + av_hash_update(ctx, intermediate_key, 20); + av_hash_update(ctx, activation_bytes, 4); + av_hash_final(ctx, intermediate_iv); + av_hash_init(ctx); + av_hash_update(ctx, intermediate_key, 16); + av_hash_update(ctx, intermediate_iv, 16); + av_hash_final(ctx, calculated_checksum); + if (memcmp(calculated_checksum, file_checksum, 20)) { + av_log(c->fc, AV_LOG_ERROR, "[aax] mismatch in checksums, terminating!\n"); + exit(-1); + } + av_aes_init(aes_decrypt, intermediate_key, 128, 1); + av_aes_crypt(aes_decrypt, output, input, DRM_BLOB_SIZE, intermediate_iv, 1); + for (i = 0; i < 4; i++) { + if (activation_bytes[i] != output[3 - i]) { + av_log(c->fc, AV_LOG_ERROR, "[aax] error in drm blob decryption, terminating!\n"); + exit(-1); + } + } + memcpy(file_key, output + 8, 16); + memcpy(input, output + 26, 16); + av_hash_init(ctx); + av_hash_update(ctx, input, 16); + av_hash_update(ctx, file_key, 16); + av_hash_update(ctx, fixed_key, 16); + av_hash_final(ctx, file_iv); + hex_encode(file_key, 16, file_key_encoded); + av_log(c->fc, AV_LOG_DEBUG, "[aax] file key == %s\n", file_key_encoded); + hex_encode(file_iv, 16, file_iv_encoded); + av_log(c->fc, AV_LOG_DEBUG, "[aax] file iv == %s\n", file_iv_encoded); + + 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) { @@ -814,6 +939,7 @@ static int mov_read_ftyp(MOVContext *c, AVIOContext *pb, MOVAtom atom) int comp_brand_size; char* comp_brands_str; uint8_t type[5] = {0}; + int64_t current_pos = avio_tell(pb); int ret = ffio_read_size(pb, type, 4); if (ret < 0) return ret; @@ -822,6 +948,13 @@ static int mov_read_ftyp(MOVContext *c, AVIOContext *pb, MOVAtom atom) c->isom = 1; av_log(c->fc, AV_LOG_DEBUG, "ISO: File Type Major Brand: %.4s\n",(char *)&type); av_dict_set(&c->fc->metadata, "major_brand", type, 0); + /* Recognize Audible AAX files */ + if (!strncmp((char*)&type, "aax", 3)) { + av_log(c->fc, AV_LOG_DEBUG, "[aax] aax file detected!\n"); + aax_mode = 1; + aax_parser(c, pb); + avio_seek(pb, current_pos, 0); + } minor_ver = avio_rb32(pb); /* minor version */ av_dict_set_int(&c->fc->metadata, "minor_version", minor_ver, 0); @@ -4336,6 +4469,27 @@ static int should_retry(AVIOContext *pb, int error_code) { return 1; } +/* Audible AAX (and AAX+) bytestream decryption + * + * export activation_bytes=CAFED00D # only 4 bytes ;) + * + * ffmpeg -i test.aax -vn -c:a copy -v debug output.mp4 + */ +static int aax_filter(uint8_t *input, int size, MOVContext *mov) +{ + int blocks = 0; + unsigned char key[16]; + unsigned char iv[16]; + + memcpy(key, file_key, 16); + memcpy(iv, file_iv, 16); + blocks = size >> 4; + av_aes_init(aes_decrypt, key, 128, 1); + av_aes_crypt(aes_decrypt, input, input, blocks, iv, 1); + + return 0; +} + static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) { MOVContext *mov = s->priv_data; @@ -4344,6 +4498,7 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt) AVStream *st = NULL; int ret; mov->fc = s; + retry: sample = mov_find_next_sample(s, &st); if (!sample) { @@ -4430,6 +4585,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 (aax_mode) + aax_filter(pkt->data, pkt->size, mov); + return 0; } -- 1.7.10.4
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel