DRM Video decryption
authorIain Wade <iwade@optusnet.com.au>
Tue, 4 Nov 2008 07:35:24 +0000 (18:35 +1100)
committerLaurent Aimar <fenrir@videolan.org>
Sat, 29 Nov 2008 11:34:58 +0000 (12:34 +0100)
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: Iain Wade <iwade@optusnet.com.au>
Signed-off-by: Laurent Aimar <fenrir@videolan.org>
modules/demux/mp4/drms.c
modules/demux/mp4/drms.h
modules/demux/mp4/libmp4.c
modules/demux/mp4/libmp4.h
modules/demux/mp4/mp4.c

index c75cf27..686c36b 100644 (file)
@@ -250,10 +250,10 @@ void drms_free( void *_p_drms )
 /*****************************************************************************
  * 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;
-    uint32_t p_key[ 4 ];
+    uint32_t p_key_buf[ 4 ];
     unsigned int i_blocks;
 
     /* 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 )
     i_bytes = i_blocks * 16;
 
     /* Initialise the key */
-    memcpy( p_key, p_drms->p_key, 16 );
+    if( !p_key )
+    {
+        p_key = p_key_buf;
+        memcpy( p_key, p_drms->p_key, 16 );
+    }
 
     /* Unscramble */
     while( i_blocks-- )
@@ -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
  *****************************************************************************
@@ -377,7 +391,7 @@ int drms_init( void *_p_drms, uint32_t i_type,
 
             memcpy( p_priv, p_info, 64 );
             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 );
 
             if( p_priv[ 0 ] != 0x6e757469 ) /* itun */
@@ -2085,7 +2099,8 @@ static int GetiPodID( int64_t *p_ipod_id )
 
 void *drms_alloc( const char *psz_homedir ){ return 0; }
 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; }
 
 #endif /* defined( UNDER_CE ) */
index 0635f40..4401846 100644 (file)
@@ -29,6 +29,7 @@ extern void drms_free( void *p_drms );
 extern int drms_init( void *p_drms, uint32_t i_type,
                       uint8_t *p_info, uint32_t i_len );
 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
index 71a9837..f65a3c8 100644 (file)
@@ -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 );
 
     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 );
 
 #ifdef MP4_VERBOSE
@@ -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 )
 {
     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 )
@@ -2106,21 +2126,59 @@ static int MP4_ReadBox_rmvc( stream_t *p_stream, MP4_Box_t *p_box )
     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 )
 {
     MP4_Box_t *p_drms_box = p_box;
+    void *p_drms = NULL;
 
     MP4_READBOX_ENTER( uint8_t );
 
     do
     {
         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,
-                               p_box->i_type, p_peek, i_read );
+        int i_ret = drms_init( p_drms, p_box->i_type, p_peek, i_read );
         if( i_ret )
         {
             const char *psz_error;
@@ -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)",
                         (char *)&p_box->i_type+1, psz_error );
 
-            drms_free( p_drms_box->data.p_sample_soun->p_drms );
-            p_drms_box->data.p_sample_soun->p_drms = NULL;
+            drms_free( p_drms );
+
+            if( p_drms_box->i_type == FOURCC_drms )
+                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
     { FOURCC_OggS,  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_mp4v,  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
     { FOURCC_iviv,  MP4_ReadBox_drms,           MP4_FreeBox_Common },
     { FOURCC_name,  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 */
     { FOURCC_0xa9nam,MP4_ReadBox_0xa9xxx,       MP4_FreeBox_0xa9xxx },
index 9073ba9..ec1b899 100644 (file)
 #define FOURCC_iviv VLC_FOURCC( 'i', 'v', 'i', 'v' )
 #define FOURCC_name VLC_FOURCC( 'n', 'a', 'm', 'e' )
 #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_tx3g VLC_FOURCC( 't', 'x', '3', 'g' )
@@ -468,6 +471,8 @@ typedef struct MP4_Box_data_sample_vide_s
     int     i_qt_image_description;
     uint8_t *p_qt_image_description;
 
+    void    *p_drms;
+
 } MP4_Box_data_sample_vide_t;
 
 #define MP4_TEXT_DISPLAY_FLAG_DONT_DISPLAY       (1<<0)
@@ -744,6 +749,18 @@ typedef struct MP4_Box_data_cmov_s
 
 } 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
 {
     uint8_t  i_version;
@@ -892,6 +909,9 @@ typedef union MP4_Box_data_s
 
     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_rmdr_t *p_rmdr;
     MP4_Box_data_rmqu_t *p_rmqu;
index 0f317dc..26f3ea1 100644 (file)
@@ -141,6 +141,7 @@ typedef struct
 
     bool b_drms;
     void      *p_drms;
+    MP4_Box_t *p_skcr;
 
 } mp4_track_t;
 
@@ -661,8 +662,24 @@ static int Demux( demux_t *p_demux )
 
                 if( tk->b_drms && tk->p_drms )
                 {
-                    drms_decrypt( tk->p_drms, (uint32_t*)p_block->p_buffer,
-                                  p_block->i_buffer );
+                    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,
+                                      p_block->i_buffer, NULL );
+                    }
                 }
                 else if( tk->fmt.i_cat == SPU_ES )
                 {
@@ -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_esds;
     MP4_Box_t   *p_box;
+    MP4_Box_t   *p_frma;
 
     if( pp_es )
         *pp_es = NULL;
@@ -1408,6 +1426,13 @@ static int TrackCreateES( demux_t *p_demux, mp4_track_t *p_track,
 
     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 ) )
     {
         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,
     p_track->p_drms = p_track->b_drms ?
         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 */
     if( *language && strcmp( language, "```" ) && strcmp( language, "und" ) )
     {