Commit b0068f14 authored by Iain Wade's avatar Iain Wade Committed by Laurent Aimar

DRM Video decryption

The attached patch adds support for decrypting and playing drm
protected video, such as those from iTunes movie store.

The patch was created against the 0.9.5 source tarball, and was
compiled and tested on OSX against some (legitimately purchased)
iTunes purchased songs and movies.

Just like the existing audio support it requires decryption keys to be
deposited in ~/.drms/ and extracting the keys is left as an exercise
for the user.

[Commit message edited by Laurent Aimar]
Signed-off-by: default avatarIain Wade <iwade@optusnet.com.au>
Signed-off-by: default avatarLaurent Aimar <fenrir@videolan.org>
parent 885069c5
...@@ -250,10 +250,10 @@ void drms_free( void *_p_drms ) ...@@ -250,10 +250,10 @@ void drms_free( void *_p_drms )
/***************************************************************************** /*****************************************************************************
* drms_decrypt: unscramble a chunk of data * drms_decrypt: unscramble a chunk of data
*****************************************************************************/ *****************************************************************************/
void drms_decrypt( void *_p_drms, uint32_t *p_buffer, uint32_t i_bytes ) void drms_decrypt( void *_p_drms, uint32_t *p_buffer, uint32_t i_bytes, uint32_t *p_key )
{ {
struct drms_s *p_drms = (struct drms_s *)_p_drms; struct drms_s *p_drms = (struct drms_s *)_p_drms;
uint32_t p_key[ 4 ]; uint32_t p_key_buf[ 4 ];
unsigned int i_blocks; unsigned int i_blocks;
/* AES is a block cypher, round down the byte count */ /* AES is a block cypher, round down the byte count */
...@@ -261,7 +261,11 @@ void drms_decrypt( void *_p_drms, uint32_t *p_buffer, uint32_t i_bytes ) ...@@ -261,7 +261,11 @@ void drms_decrypt( void *_p_drms, uint32_t *p_buffer, uint32_t i_bytes )
i_bytes = i_blocks * 16; i_bytes = i_blocks * 16;
/* Initialise the key */ /* Initialise the key */
if( !p_key )
{
p_key = p_key_buf;
memcpy( p_key, p_drms->p_key, 16 ); memcpy( p_key, p_drms->p_key, 16 );
}
/* Unscramble */ /* Unscramble */
while( i_blocks-- ) while( i_blocks-- )
...@@ -283,6 +287,16 @@ void drms_decrypt( void *_p_drms, uint32_t *p_buffer, uint32_t i_bytes ) ...@@ -283,6 +287,16 @@ void drms_decrypt( void *_p_drms, uint32_t *p_buffer, uint32_t i_bytes )
} }
} }
/*****************************************************************************
* drms_get_p_key: copy the p_key into user buffer
****************************************************************************/
void drms_get_p_key( void *_p_drms, uint32_t *p_key )
{
struct drms_s *p_drms = (struct drms_s *)_p_drms;
memcpy( p_key, p_drms->p_key, 16 );
}
/***************************************************************************** /*****************************************************************************
* drms_init: initialise a DRMS structure * drms_init: initialise a DRMS structure
***************************************************************************** *****************************************************************************
...@@ -377,7 +391,7 @@ int drms_init( void *_p_drms, uint32_t i_type, ...@@ -377,7 +391,7 @@ int drms_init( void *_p_drms, uint32_t i_type,
memcpy( p_priv, p_info, 64 ); memcpy( p_priv, p_info, 64 );
memcpy( p_drms->p_key, md5.p_digest, 16 ); memcpy( p_drms->p_key, md5.p_digest, 16 );
drms_decrypt( p_drms, p_priv, 64 ); drms_decrypt( p_drms, p_priv, 64, NULL );
REVERSE( p_priv, 64 ); REVERSE( p_priv, 64 );
if( p_priv[ 0 ] != 0x6e757469 ) /* itun */ if( p_priv[ 0 ] != 0x6e757469 ) /* itun */
...@@ -2085,7 +2099,8 @@ static int GetiPodID( int64_t *p_ipod_id ) ...@@ -2085,7 +2099,8 @@ static int GetiPodID( int64_t *p_ipod_id )
void *drms_alloc( const char *psz_homedir ){ return 0; } void *drms_alloc( const char *psz_homedir ){ return 0; }
void drms_free( void *a ){} void drms_free( void *a ){}
void drms_decrypt( void *a, uint32_t *b, uint32_t c ){} void drms_decrypt( void *a, uint32_t *b, uint32_t c, uint32_t *k ){}
void drms_get_p_key( void *p_drms, uint32_t *p_key );
int drms_init( void *a, uint32_t b, uint8_t *c, uint32_t d ){ return -1; } int drms_init( void *a, uint32_t b, uint8_t *c, uint32_t d ){ return -1; }
#endif /* defined( UNDER_CE ) */ #endif /* defined( UNDER_CE ) */
...@@ -29,6 +29,7 @@ extern void drms_free( void *p_drms ); ...@@ -29,6 +29,7 @@ extern void drms_free( void *p_drms );
extern int drms_init( void *p_drms, uint32_t i_type, extern int drms_init( void *p_drms, uint32_t i_type,
uint8_t *p_info, uint32_t i_len ); uint8_t *p_info, uint32_t i_len );
extern void drms_decrypt( void *p_drms, uint32_t *p_buffer, extern void drms_decrypt( void *p_drms, uint32_t *p_buffer,
uint32_t i_len ); uint32_t i_len, uint32_t *p_key );
extern void drms_get_p_key( void *p_drms, uint32_t *p_key );
#endif #endif
...@@ -1316,6 +1316,18 @@ int MP4_ReadBox_sample_vide( stream_t *p_stream, MP4_Box_t *p_box ) ...@@ -1316,6 +1316,18 @@ int MP4_ReadBox_sample_vide( stream_t *p_stream, MP4_Box_t *p_box )
MP4_GET2BYTES( p_box->data.p_sample_vide->i_qt_color_table ); MP4_GET2BYTES( p_box->data.p_sample_vide->i_qt_color_table );
stream_Seek( p_stream, p_box->i_pos + MP4_BOX_HEADERSIZE( p_box ) + 78); stream_Seek( p_stream, p_box->i_pos + MP4_BOX_HEADERSIZE( p_box ) + 78);
if( p_box->i_type == FOURCC_drmi )
{
p_box->data.p_sample_vide->p_drms =
drms_alloc( config_GetHomeDir() );
if( p_box->data.p_sample_vide->p_drms == NULL )
{
msg_Err( p_stream, "drms_alloc() failed" );
}
}
MP4_ReadBoxContainerRaw( p_stream, p_box ); MP4_ReadBoxContainerRaw( p_stream, p_box );
#ifdef MP4_VERBOSE #ifdef MP4_VERBOSE
...@@ -1332,6 +1344,14 @@ int MP4_ReadBox_sample_vide( stream_t *p_stream, MP4_Box_t *p_box ) ...@@ -1332,6 +1344,14 @@ int MP4_ReadBox_sample_vide( stream_t *p_stream, MP4_Box_t *p_box )
void MP4_FreeBox_sample_vide( MP4_Box_t *p_box ) void MP4_FreeBox_sample_vide( MP4_Box_t *p_box )
{ {
FREENULL( p_box->data.p_sample_vide->p_qt_image_description ); FREENULL( p_box->data.p_sample_vide->p_qt_image_description );
if( p_box->i_type == FOURCC_drmi )
{
if( p_box->data.p_sample_vide->p_drms )
{
drms_free( p_box->data.p_sample_vide->p_drms );
}
}
} }
static int MP4_ReadBox_sample_mp4s( stream_t *p_stream, MP4_Box_t *p_box ) static int MP4_ReadBox_sample_mp4s( stream_t *p_stream, MP4_Box_t *p_box )
...@@ -2106,21 +2126,59 @@ static int MP4_ReadBox_rmvc( stream_t *p_stream, MP4_Box_t *p_box ) ...@@ -2106,21 +2126,59 @@ static int MP4_ReadBox_rmvc( stream_t *p_stream, MP4_Box_t *p_box )
MP4_READBOX_EXIT( 1 ); MP4_READBOX_EXIT( 1 );
} }
static int MP4_ReadBox_frma( stream_t *p_stream, MP4_Box_t *p_box )
{
MP4_READBOX_ENTER( MP4_Box_data_frma_t );
MP4_GETFOURCC( p_box->data.p_frma->i_type );
#ifdef MP4_VERBOSE
msg_Dbg( p_stream, "read box: \"frma\" i_type:%4.4s",
(char *)&p_box->data.p_frma->i_type );
#endif
MP4_READBOX_EXIT( 1 );
}
static int MP4_ReadBox_skcr( stream_t *p_stream, MP4_Box_t *p_box )
{
MP4_READBOX_ENTER( MP4_Box_data_frma_t );
MP4_GET4BYTES( p_box->data.p_skcr->i_init );
MP4_GET4BYTES( p_box->data.p_skcr->i_encr );
MP4_GET4BYTES( p_box->data.p_skcr->i_decr );
#ifdef MP4_VERBOSE
msg_Dbg( p_stream, "read box: \"skcr\" i_init:%d i_encr:%d i_decr:%d",
p_box->data.p_skcr->i_init,
p_box->data.p_skcr->i_encr,
p_box->data.p_skcr->i_decr );
#endif
MP4_READBOX_EXIT( 1 );
}
static int MP4_ReadBox_drms( stream_t *p_stream, MP4_Box_t *p_box ) static int MP4_ReadBox_drms( stream_t *p_stream, MP4_Box_t *p_box )
{ {
MP4_Box_t *p_drms_box = p_box; MP4_Box_t *p_drms_box = p_box;
void *p_drms = NULL;
MP4_READBOX_ENTER( uint8_t ); MP4_READBOX_ENTER( uint8_t );
do do
{ {
p_drms_box = p_drms_box->p_father; p_drms_box = p_drms_box->p_father;
} while( p_drms_box && p_drms_box->i_type != FOURCC_drms ); } while( p_drms_box && p_drms_box->i_type != FOURCC_drms
&& p_drms_box->i_type != FOURCC_drmi );
if( p_drms_box && p_drms_box->data.p_sample_soun->p_drms ) if( p_drms_box && p_drms_box->i_type == FOURCC_drms )
p_drms = p_drms_box->data.p_sample_soun->p_drms;
else if( p_drms_box && p_drms_box->i_type == FOURCC_drmi )
p_drms = p_drms_box->data.p_sample_vide->p_drms;
if( p_drms_box && p_drms )
{ {
int i_ret = drms_init( p_drms_box->data.p_sample_soun->p_drms, int i_ret = drms_init( p_drms, p_box->i_type, p_peek, i_read );
p_box->i_type, p_peek, i_read );
if( i_ret ) if( i_ret )
{ {
const char *psz_error; const char *psz_error;
...@@ -2142,8 +2200,12 @@ static int MP4_ReadBox_drms( stream_t *p_stream, MP4_Box_t *p_box ) ...@@ -2142,8 +2200,12 @@ static int MP4_ReadBox_drms( stream_t *p_stream, MP4_Box_t *p_box )
msg_Err( p_stream, "drms_init(c%3.3s) failed (%s)", msg_Err( p_stream, "drms_init(c%3.3s) failed (%s)",
(char *)&p_box->i_type+1, psz_error ); (char *)&p_box->i_type+1, psz_error );
drms_free( p_drms_box->data.p_sample_soun->p_drms ); drms_free( p_drms );
if( p_drms_box->i_type == FOURCC_drms )
p_drms_box->data.p_sample_soun->p_drms = NULL; p_drms_box->data.p_sample_soun->p_drms = NULL;
else if( p_drms_box->i_type == FOURCC_drmi )
p_drms_box->data.p_sample_vide->p_drms = NULL;
} }
} }
...@@ -2497,6 +2559,7 @@ static const struct ...@@ -2497,6 +2559,7 @@ static const struct
{ FOURCC_OggS, MP4_ReadBox_sample_soun, MP4_FreeBox_sample_soun }, { FOURCC_OggS, MP4_ReadBox_sample_soun, MP4_FreeBox_sample_soun },
{ FOURCC_alac, MP4_ReadBox_sample_soun, MP4_FreeBox_sample_soun }, { FOURCC_alac, MP4_ReadBox_sample_soun, MP4_FreeBox_sample_soun },
{ FOURCC_drmi, MP4_ReadBox_sample_vide, MP4_FreeBox_sample_vide },
{ FOURCC_vide, MP4_ReadBox_sample_vide, MP4_FreeBox_sample_vide }, { FOURCC_vide, MP4_ReadBox_sample_vide, MP4_FreeBox_sample_vide },
{ FOURCC_mp4v, MP4_ReadBox_sample_vide, MP4_FreeBox_sample_vide }, { FOURCC_mp4v, MP4_ReadBox_sample_vide, MP4_FreeBox_sample_vide },
{ FOURCC_SVQ1, MP4_ReadBox_sample_vide, MP4_FreeBox_sample_vide }, { FOURCC_SVQ1, MP4_ReadBox_sample_vide, MP4_FreeBox_sample_vide },
...@@ -2566,6 +2629,8 @@ static const struct ...@@ -2566,6 +2629,8 @@ static const struct
{ FOURCC_iviv, MP4_ReadBox_drms, MP4_FreeBox_Common }, { FOURCC_iviv, MP4_ReadBox_drms, MP4_FreeBox_Common },
{ FOURCC_name, MP4_ReadBox_drms, MP4_FreeBox_Common }, { FOURCC_name, MP4_ReadBox_drms, MP4_FreeBox_Common },
{ FOURCC_priv, MP4_ReadBox_drms, MP4_FreeBox_Common }, { FOURCC_priv, MP4_ReadBox_drms, MP4_FreeBox_Common },
{ FOURCC_frma, MP4_ReadBox_frma, MP4_FreeBox_Common },
{ FOURCC_skcr, MP4_ReadBox_skcr, MP4_FreeBox_Common },
/* found in udta */ /* found in udta */
{ FOURCC_0xa9nam,MP4_ReadBox_0xa9xxx, MP4_FreeBox_0xa9xxx }, { FOURCC_0xa9nam,MP4_ReadBox_0xa9xxx, MP4_FreeBox_0xa9xxx },
......
...@@ -180,6 +180,9 @@ ...@@ -180,6 +180,9 @@
#define FOURCC_iviv VLC_FOURCC( 'i', 'v', 'i', 'v' ) #define FOURCC_iviv VLC_FOURCC( 'i', 'v', 'i', 'v' )
#define FOURCC_name VLC_FOURCC( 'n', 'a', 'm', 'e' ) #define FOURCC_name VLC_FOURCC( 'n', 'a', 'm', 'e' )
#define FOURCC_priv VLC_FOURCC( 'p', 'r', 'i', 'v' ) #define FOURCC_priv VLC_FOURCC( 'p', 'r', 'i', 'v' )
#define FOURCC_drmi VLC_FOURCC( 'd', 'r', 'm', 'i' )
#define FOURCC_frma VLC_FOURCC( 'f', 'r', 'm', 'a' )
#define FOURCC_skcr VLC_FOURCC( 's', 'k', 'c', 'r' )
#define FOURCC_text VLC_FOURCC( 't', 'e', 'x', 't' ) #define FOURCC_text VLC_FOURCC( 't', 'e', 'x', 't' )
#define FOURCC_tx3g VLC_FOURCC( 't', 'x', '3', 'g' ) #define FOURCC_tx3g VLC_FOURCC( 't', 'x', '3', 'g' )
...@@ -468,6 +471,8 @@ typedef struct MP4_Box_data_sample_vide_s ...@@ -468,6 +471,8 @@ typedef struct MP4_Box_data_sample_vide_s
int i_qt_image_description; int i_qt_image_description;
uint8_t *p_qt_image_description; uint8_t *p_qt_image_description;
void *p_drms;
} MP4_Box_data_sample_vide_t; } MP4_Box_data_sample_vide_t;
#define MP4_TEXT_DISPLAY_FLAG_DONT_DISPLAY (1<<0) #define MP4_TEXT_DISPLAY_FLAG_DONT_DISPLAY (1<<0)
...@@ -744,6 +749,18 @@ typedef struct MP4_Box_data_cmov_s ...@@ -744,6 +749,18 @@ typedef struct MP4_Box_data_cmov_s
} MP4_Box_data_cmov_t; } MP4_Box_data_cmov_t;
typedef struct
{
uint32_t i_type;
} MP4_Box_data_frma_t;
typedef struct
{
uint32_t i_init;
uint32_t i_encr;
uint32_t i_decr;
} MP4_Box_data_skcr_t;
typedef struct typedef struct
{ {
uint8_t i_version; uint8_t i_version;
...@@ -892,6 +909,9 @@ typedef union MP4_Box_data_s ...@@ -892,6 +909,9 @@ typedef union MP4_Box_data_s
MP4_Box_data_moviehintinformation_rtp_t p_moviehintinformation_rtp; MP4_Box_data_moviehintinformation_rtp_t p_moviehintinformation_rtp;
MP4_Box_data_frma_t *p_frma;
MP4_Box_data_skcr_t *p_skcr;
MP4_Box_data_rdrf_t *p_rdrf; MP4_Box_data_rdrf_t *p_rdrf;
MP4_Box_data_rmdr_t *p_rmdr; MP4_Box_data_rmdr_t *p_rmdr;
MP4_Box_data_rmqu_t *p_rmqu; MP4_Box_data_rmqu_t *p_rmqu;
......
...@@ -141,6 +141,7 @@ typedef struct ...@@ -141,6 +141,7 @@ typedef struct
bool b_drms; bool b_drms;
void *p_drms; void *p_drms;
MP4_Box_t *p_skcr;
} mp4_track_t; } mp4_track_t;
...@@ -660,9 +661,25 @@ static int Demux( demux_t *p_demux ) ...@@ -660,9 +661,25 @@ static int Demux( demux_t *p_demux )
} }
if( tk->b_drms && tk->p_drms ) if( tk->b_drms && tk->p_drms )
{
if( tk->p_skcr )
{
uint32_t p_key[4];
drms_get_p_key( tk->p_drms, p_key );
for( int i_pos = tk->p_skcr->data.p_skcr->i_init; i_pos < p_block->i_buffer; )
{
int n = __MIN( tk->p_skcr->data.p_skcr->i_encr, p_block->i_buffer - i_pos );
drms_decrypt( tk->p_drms, (uint32_t*)&p_block->p_buffer[i_pos], n, p_key );
i_pos += n;
i_pos += __MIN( tk->p_skcr->data.p_skcr->i_decr, p_block->i_buffer - i_pos );
}
}
else
{ {
drms_decrypt( tk->p_drms, (uint32_t*)p_block->p_buffer, drms_decrypt( tk->p_drms, (uint32_t*)p_block->p_buffer,
p_block->i_buffer ); p_block->i_buffer, NULL );
}
} }
else if( tk->fmt.i_cat == SPU_ES ) else if( tk->fmt.i_cat == SPU_ES )
{ {
...@@ -1384,6 +1401,7 @@ static int TrackCreateES( demux_t *p_demux, mp4_track_t *p_track, ...@@ -1384,6 +1401,7 @@ static int TrackCreateES( demux_t *p_demux, mp4_track_t *p_track,
MP4_Box_t *p_sample; MP4_Box_t *p_sample;
MP4_Box_t *p_esds; MP4_Box_t *p_esds;
MP4_Box_t *p_box; MP4_Box_t *p_box;
MP4_Box_t *p_frma;
if( pp_es ) if( pp_es )
*pp_es = NULL; *pp_es = NULL;
...@@ -1408,6 +1426,13 @@ static int TrackCreateES( demux_t *p_demux, mp4_track_t *p_track, ...@@ -1408,6 +1426,13 @@ static int TrackCreateES( demux_t *p_demux, mp4_track_t *p_track,
p_track->p_sample = p_sample; p_track->p_sample = p_sample;
if( ( p_frma = MP4_BoxGet( p_track->p_sample, "sinf/frma" ) ) )
{
msg_Warn( p_demux, "Original Format Box: %4.4s", (char *)&p_frma->data.p_frma->i_type );
p_sample->i_type = p_frma->data.p_frma->i_type;
}
if( p_track->fmt.i_cat == AUDIO_ES && ( p_track->i_sample_size == 1 || p_track->i_sample_size == 2 ) ) if( p_track->fmt.i_cat == AUDIO_ES && ( p_track->i_sample_size == 1 || p_track->i_sample_size == 2 ) )
{ {
MP4_Box_data_sample_soun_t *p_soun; MP4_Box_data_sample_soun_t *p_soun;
...@@ -2143,6 +2168,17 @@ static void MP4_TrackCreate( demux_t *p_demux, mp4_track_t *p_track, ...@@ -2143,6 +2168,17 @@ static void MP4_TrackCreate( demux_t *p_demux, mp4_track_t *p_track,
p_track->p_drms = p_track->b_drms ? p_track->p_drms = p_track->b_drms ?
p_drms->data.p_sample_soun->p_drms : NULL; p_drms->data.p_sample_soun->p_drms : NULL;
if ( !p_drms )
{
p_drms = MP4_BoxGet( p_track->p_stsd, "drmi" );
p_track->b_drms = p_drms != NULL;
p_track->p_drms = p_track->b_drms ?
p_drms->data.p_sample_vide->p_drms : NULL;
}
if( p_drms )
p_track->p_skcr = MP4_BoxGet( p_drms, "sinf/skcr" );
/* Set language */ /* Set language */
if( *language && strcmp( language, "```" ) && strcmp( language, "und" ) ) if( *language && strcmp( language, "```" ) && strcmp( language, "und" ) )
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment