DCP: read encrypted DCP with KDM files
authorSimona-Marinela Prodea <simona.marinela.prodea@gmail.com>
Tue, 29 Apr 2014 16:31:35 +0000 (18:31 +0200)
committerJean-Baptiste Kempf <jb@videolan.org>
Sat, 3 May 2014 16:39:35 +0000 (18:39 +0200)
Uses libgcrypt

Signed-off-by: Jean-Baptiste Kempf <jb@videolan.org>
modules/access/Makefile.am
modules/access/dcp/dcp.cpp
modules/access/dcp/dcpdecrypt.cpp [new file with mode: 0644]
modules/access/dcp/dcpparser.cpp
modules/access/dcp/dcpparser.h

index a5b3679..40d65dc 100644 (file)
@@ -9,12 +9,16 @@ AM_CPPFLAGS += -I$(srcdir)/access
 libattachment_plugin_la_SOURCES = access/attachment.c
 access_LTLIBRARIES += libattachment_plugin.la
 
-libdcp_plugin_la_SOURCES = access/dcp/dcpparser.h access/dcp/dcp.cpp access/dcp/dcpparser.cpp
+libdcp_plugin_la_SOURCES = access/dcp/dcpparser.h access/dcp/dcp.cpp access/dcp/dcpparser.cpp access/dcp/dcpdecrypt.cpp
 if HAVE_ASDCP
 libdcp_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(ASDCP_CFLAGS)
 libdcp_plugin_la_LIBADD = $(AM_LIBADD) $(ASDCP_LIBS)
+if HAVE_GCRYPT
+libdcp_plugin_la_CPPFLAGS += $(GCRYPT_CFLAGS)
+libdcp_plugin_la_LIBADD += $(GCRYPT_LIBS)
 access_LTLIBRARIES += libdcp_plugin.la
 endif
+endif
 
 libfilesystem_plugin_la_SOURCES = access/fs.h access/file.c access/directory.c access/fs.c
 libfilesystem_plugin_la_CPPFLAGS = $(AM_CPPFLAGS)
index 7960c6e..caf3f36 100644 (file)
@@ -41,6 +41,8 @@
 #endif
 
 #define __STDC_CONSTANT_MACROS 1
+#define KDM_HELP_TEXT          "KDM file"
+#define KDM_HELP_LONG_TEXT     "Path to Key Delivery Message XML file"
 
 /* VLC core API headers */
 #include <vlc_common.h>
@@ -72,6 +74,7 @@ static void Close( vlc_object_t * );
 vlc_module_begin()
     set_shortname( N_( "DCP" ) )
     add_shortcut( "dcp" )
+    add_loadfile( "kdm", "", KDM_HELP_TEXT, KDM_HELP_LONG_TEXT, false )
     set_description( N_( "Digital Cinema Package module" ) )
     set_capability( "access_demux", 0 )
     set_category( CAT_INPUT )
@@ -604,6 +607,7 @@ static int Demux( demux_t *p_demux )
     block_t *p_video_frame = NULL, *p_audio_frame = NULL;
 
     PCM::FrameBuffer   AudioFrameBuff( p_sys->i_audio_buffer);
+    AESDecContext video_aes_ctx, audio_aes_ctx;
 
     /* swaping video reels */
     if  ( p_sys->frame_no == p_sys->p_dcp->video_reels[p_sys->i_video_reel].i_absolute_end )
@@ -632,6 +636,20 @@ static int Demux( demux_t *p_demux )
      }
 
     /* video frame */
+
+    /* initialize AES context, if reel is encrypted */
+    if( p_sys &&
+            p_sys->p_dcp &&
+            p_sys->p_dcp->video_reels.size() > p_sys->i_video_reel &&
+            p_sys->p_dcp->video_reels[p_sys->i_video_reel].p_key )
+    {
+        if( ! ASDCP_SUCCESS( video_aes_ctx.InitKey( p_sys->p_dcp->video_reels[p_sys->i_video_reel].p_key->getKey() ) ) )
+        {
+            msg_Err( p_demux, "ASDCP failed to initialize AES key" );
+            goto error;
+        }
+    }
+
     switch( p_sys->PictureEssType )
     {
         case ESS_JPEG_2000:
@@ -646,13 +664,13 @@ static int Demux( demux_t *p_demux )
                 goto error_asdcp;
             if ( p_sys->PictureEssType == ESS_JPEG_2000_S ) {
                 if ( ! ASDCP_SUCCESS(
-                        p_sys->v_videoReader[p_sys->i_video_reel].p_PicMXFSReader->ReadFrame(nextFrame, JP2K::SP_LEFT, PicFrameBuff, 0, 0)) ) {
+                        p_sys->v_videoReader[p_sys->i_video_reel].p_PicMXFSReader->ReadFrame(nextFrame, JP2K::SP_LEFT, PicFrameBuff, &video_aes_ctx, 0)) ) {
                     PicFrameBuff.SetData(0,0);
                     goto error_asdcp;
                 }
              } else {
                 if ( ! ASDCP_SUCCESS(
-                        p_sys->v_videoReader[p_sys->i_video_reel].p_PicMXFReader->ReadFrame(nextFrame, PicFrameBuff, 0, 0)) ) {
+                        p_sys->v_videoReader[p_sys->i_video_reel].p_PicMXFReader->ReadFrame(nextFrame, PicFrameBuff, &video_aes_ctx, 0)) ) {
                     PicFrameBuff.SetData(0,0);
                     goto error_asdcp;
                 }
@@ -670,7 +688,7 @@ static int Demux( demux_t *p_demux )
                 goto error_asdcp;
 
             if ( ! ASDCP_SUCCESS(
-                    p_sys->v_videoReader[p_sys->i_video_reel].p_VideoMXFReader->ReadFrame(p_sys->frame_no + p_sys->p_dcp->video_reels[p_sys->i_video_reel].i_correction, VideoFrameBuff, 0, 0)) ) {
+                    p_sys->v_videoReader[p_sys->i_video_reel].p_VideoMXFReader->ReadFrame(p_sys->frame_no + p_sys->p_dcp->video_reels[p_sys->i_video_reel].i_correction, VideoFrameBuff, &video_aes_ctx, 0)) ) {
                 VideoFrameBuff.SetData(0,0);
                 goto error_asdcp;
             }
@@ -690,13 +708,27 @@ static int Demux( demux_t *p_demux )
     if ( ( p_audio_frame = block_Alloc( p_sys->i_audio_buffer )) == NULL ) {
         goto error;
     }
+
+    /* initialize AES context, if reel is encrypted */
+    if( p_sys &&
+            p_sys->p_dcp &&
+            p_sys->p_dcp->audio_reels.size() > p_sys->i_audio_reel &&
+            p_sys->p_dcp->audio_reels[p_sys->i_audio_reel].p_key )
+    {
+        if( ! ASDCP_SUCCESS( audio_aes_ctx.InitKey( p_sys->p_dcp->audio_reels[p_sys->i_audio_reel].p_key->getKey() ) ) )
+        {
+            msg_Err( p_demux, "ASDCP failed to initialize AES key" );
+            goto error;
+        }
+    }
+
     if ( ! ASDCP_SUCCESS(
             AudioFrameBuff.SetData(p_audio_frame->p_buffer, p_sys->i_audio_buffer)) ) {
         goto error_asdcp;
     }
 
     if ( ! ASDCP_SUCCESS(
-            p_sys->v_audioReader[p_sys->i_audio_reel].p_AudioMXFReader->ReadFrame(p_sys->frame_no + p_sys->p_dcp->audio_reels[p_sys->i_audio_reel].i_correction, AudioFrameBuff, 0, 0)) ) {
+            p_sys->v_audioReader[p_sys->i_audio_reel].p_AudioMXFReader->ReadFrame(p_sys->frame_no + p_sys->p_dcp->audio_reels[p_sys->i_audio_reel].i_correction, AudioFrameBuff, &audio_aes_ctx, 0)) ) {
         AudioFrameBuff.SetData(0,0);
         goto error_asdcp;
     }
diff --git a/modules/access/dcp/dcpdecrypt.cpp b/modules/access/dcp/dcpdecrypt.cpp
new file mode 100644 (file)
index 0000000..4590c62
--- /dev/null
@@ -0,0 +1,673 @@
+/*****************************************************************************
+ * Copyright (C) 2013 VLC authors and VideoLAN
+ *
+ * Author:
+ *          Simona-Marinela Prodea <simona dot marinela dot prodea at gmail dot com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/**
+ * The code used for reading a DER-encoded private key, that is,
+ * RSAKey::parseTag function and RSAKey::readDER function,
+ * is taken almost as is from libgcrypt tests/fipsdrv.c
+ */
+
+/**
+ * @file dcpdecrypt.cpp
+ * @brief Handle encrypted DCPs
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+/* VLC core API headers */
+#include <vlc_common.h>
+#include <vlc_xml.h>
+#include <vlc_strings.h>
+
+#include <fstream>
+#include <algorithm>
+#include <cctype>
+
+#include "dcpparser.h"
+
+/* creates a printable, RFC 4122-conform UUID, from a given array of bytes
+ */
+static string createUUID( unsigned char *ps_string )
+{
+    string s_uuid;
+    char h[3];
+    int i, ret;
+
+    if( ! ps_string )
+        return "";
+
+    try
+    {
+        s_uuid.append( "urn:uuid:" );
+        for( i = 0; i < 16; i++ )
+        {
+            ret = snprintf( h, 3, "%02hhx", ps_string[i] );  /* each byte can be written as 2 hex digits */
+            if( ret != 2 )
+                return "";
+            s_uuid.append( h );
+            if( i == 3 || i == 5 || i == 7 || i == 9 )
+                s_uuid.append( "-" );
+        }
+    }
+    catch( ... )
+    {
+        return "";
+    }
+
+    return s_uuid;
+}
+
+/*
+ * KDM class
+ */
+
+int KDM::Parse()
+{
+    string s_node, s_value;
+    const string s_root_node = "DCinemaSecurityMessage";
+    int type;
+
+    AESKeyList *_p_key_list = NULL;
+
+    /* init XML parser */
+    if( this->OpenXml() )
+    {
+        msg_Err( p_demux, "failed to initialize KDM XML parser" );
+        return VLC_EGENERIC;
+    }
+
+    msg_Dbg( this->p_demux, "parsing KDM..." );
+
+    /* read first node and check if it is a KDM */
+    if( ! ( ( XML_READER_STARTELEM == XmlFile::ReadNextNode( this->p_xmlReader, s_node ) ) && ( s_node == s_root_node ) ) )
+    {
+        msg_Err( this->p_demux, "not a valid XML KDM" );
+        goto error;
+    }
+
+    while( ( type = XmlFile::ReadNextNode( this->p_xmlReader, s_node ) ) > 0 )
+        if( type == XML_READER_STARTELEM && s_node == "AuthenticatedPrivate" )
+        {
+            _p_key_list = new (nothrow) AESKeyList;
+            if( unlikely( _p_key_list == NULL ) )
+                goto error;
+            p_dcp->p_key_list = _p_key_list;
+            if( this->ParsePrivate( s_node, type ) )
+                goto error;
+
+            /* keys found, so break */
+            break;
+        }
+
+    if ( (_p_key_list == NULL) ||  (_p_key_list->size() == 0) )
+    {
+        msg_Err( p_demux, "Key list empty" );
+        goto error;
+    }
+
+    /* close KDM XML */
+    this->CloseXml();
+    return VLC_SUCCESS;
+error:
+    this->CloseXml();
+    return VLC_EGENERIC;
+}
+
+int KDM::ParsePrivate( const string _s_node, int _i_type )
+{
+    string s_node;
+    int i_type;
+    AESKey *p_key;
+
+    /* check that we are where we're supposed to be */
+    if( _i_type != XML_READER_STARTELEM )
+        goto error;
+    if( _s_node != "AuthenticatedPrivate" )
+        goto error;
+
+    /* loop on EncryptedKey nodes */
+    while( ( i_type = XmlFile::ReadNextNode( this->p_xmlReader, s_node ) ) > 0 )
+    {
+        switch( i_type )
+        {
+            case XML_READER_STARTELEM:
+                if( s_node != "enc:EncryptedKey" )
+                    goto error;
+                p_key = new (nothrow) AESKey( this->p_demux );
+                if( unlikely( p_key == NULL ) )
+                    return VLC_EGENERIC;
+                if( p_key->Parse( p_xmlReader, s_node, i_type ) )
+                {
+                    delete p_key;
+                    return VLC_EGENERIC;
+                }
+                p_dcp->p_key_list->push_back( p_key );
+                break;
+
+            case XML_READER_ENDELEM:
+                if( s_node == _s_node )
+                    return VLC_SUCCESS;
+                break;
+            default:
+            case XML_READER_TEXT:
+                goto error;
+        }
+    }
+
+    /* shouldn't get here */
+error:
+    msg_Err( p_demux, "error while parsing AuthenticatedPrivate portion of KDM" );
+    return VLC_EGENERIC;
+}
+
+/*
+ * AESKey class
+ */
+
+int AESKey::Parse( xml_reader_t *p_xml_reader, string _s_node, int _i_type)
+{
+    string s_node;
+    string s_value;
+    int i_type;
+
+    if( _i_type != XML_READER_STARTELEM)
+        goto error;
+    if( _s_node != "enc:EncryptedKey" )
+        goto error;
+
+    while( ( i_type = XmlFile::ReadNextNode( p_xml_reader, s_node ) ) > 0 )
+    {
+        switch( i_type )
+        {
+            case XML_READER_STARTELEM:
+                if( s_node == "enc:CipherValue" )
+                {
+                    if( XmlFile::ReadEndNode( p_xml_reader, s_node, i_type, s_value ) )
+                        goto error;
+                    if( this->decryptRSA( s_value ) )
+                        return VLC_EGENERIC;
+                }
+                break;
+            case XML_READER_ENDELEM:
+                if( s_node == _s_node )
+                    return VLC_SUCCESS;
+                break;
+            default:
+            case XML_READER_TEXT:
+                goto error;
+        }
+    }
+
+    /* shouldn't get here */
+error:
+    msg_Err( this->p_demux, "error while parsing EncryptedKey" );
+    return VLC_EGENERIC;
+}
+
+/* decrypts the RSA encrypted text read from the XML file,
+ * and saves the AES key and the other needed info
+ * uses libgcrypt for decryption
+ */
+int AESKey::decryptRSA( string s_cipher_text_b64 )
+{
+    RSAKey rsa_key( this->p_demux );
+    unsigned char *ps_cipher_text = NULL;
+    unsigned char *ps_plain_text = NULL;
+    gcry_mpi_t cipher_text_mpi = NULL;
+    gcry_sexp_t cipher_text_sexp = NULL;
+    gcry_sexp_t plain_text_sexp = NULL;
+    gcry_mpi_t plain_text_mpi = NULL;
+    gcry_sexp_t tmp_sexp = NULL;
+    gcry_error_t err;
+    size_t length;
+    int i_ret = VLC_EGENERIC;
+
+    /* get RSA private key file path */
+    if( rsa_key.setPath() )
+        goto end;
+
+    /* read private key from file */
+    if( rsa_key.readPEM() )
+        goto end;
+
+    /* remove spaces and newlines from encoded cipher text
+     * (usually added for indentation in XML files)
+     * */
+    try
+    {
+        s_cipher_text_b64.erase( remove_if( s_cipher_text_b64.begin(), s_cipher_text_b64.end(), static_cast<int(*)(int)>(isspace) ),
+                                 s_cipher_text_b64.end() );
+    }
+    catch( ... )
+    {
+        msg_Err( this->p_demux, "error while handling string" );
+        goto end;
+    }
+
+    /* decode cipher from BASE64 to binary */
+    if( ! ( length = vlc_b64_decode_binary( &ps_cipher_text, s_cipher_text_b64.c_str() ) ) )
+    {
+        msg_Err( this->p_demux, "could not decode cipher from Base64" );
+        goto end;
+    }
+
+    /* initialize libgcrypt */
+    vlc_gcrypt_init ();
+
+    /* create S-expression for ciphertext */
+    if( ( err = gcry_mpi_scan( &cipher_text_mpi, GCRYMPI_FMT_USG, ps_cipher_text, 256, NULL ) ) )
+    {
+        msg_Err( this->p_demux, "could not scan MPI from cipher text: %s", gcry_strerror( err ) );
+        goto end;
+    }
+    if( ( err = gcry_sexp_build( &cipher_text_sexp, NULL, "(enc-val(flags oaep)(rsa(a %m)))", cipher_text_mpi ) ) )
+    {
+        msg_Err( this->p_demux, "could not build S-expression for cipher text: %s", gcry_strerror( err ) );
+        goto end;
+    }
+
+    /* decrypt */
+    if( ( err = gcry_pk_decrypt( &plain_text_sexp, cipher_text_sexp, rsa_key.priv_key ) ) )
+    {
+        msg_Err( this->p_demux, "error while decrypting RSA encrypted info: %s", gcry_strerror( err ) );
+        goto end;
+    }
+
+    /* extract plain-text from S-expression */
+    if( ! ( tmp_sexp = gcry_sexp_find_token( plain_text_sexp, "value", 0 ) ) )
+        /* when using padding flags, the decrypted S-expression is of the form
+         * "(value <plaintext>)", where <plaintext> is an MPI */
+    {
+        msg_Err( this->p_demux, "decrypted text is in an unexpected form; decryption may have failed" );
+        goto end;
+    }
+    /* we could have used the gcry_sexp_nth_data to get the data directly,
+     * but as that function is newly introduced (libgcrypt v1.6),
+     * we prefer compatibility, even though that means passing the data through an MPI first */
+    if( ! ( plain_text_mpi = gcry_sexp_nth_mpi( tmp_sexp, 1, GCRYMPI_FMT_USG ) ) )
+    {
+        msg_Err( this->p_demux, "could not extract MPI from decrypted S-expression" );
+        goto end;
+    }
+
+    if( ( err = gcry_mpi_aprint( GCRYMPI_FMT_USG, &ps_plain_text, &length, plain_text_mpi ) ) )
+    {
+        msg_Err( this->p_demux, "error while extracting plain text from MPI: %s", gcry_strerror( err ) );
+        goto end;
+    }
+
+    /* interpret the plaintext data */
+    switch( length )
+    {
+        case 138:   /* SMPTE    DCP */
+            if( this->extractInfo( ps_plain_text, true ) )
+                goto end;
+            break;
+        case 134:   /* Interop  DCP */
+            if( this->extractInfo( ps_plain_text, false ) )
+                goto end;
+            break;
+        case -1:
+            msg_Err( this->p_demux, "could not decrypt" );
+            goto end;
+        default:
+            msg_Err( this->p_demux, "CipherValue field length does not match SMPTE nor Interop standards" );
+            goto end;
+    }
+
+    i_ret = VLC_SUCCESS;
+
+end:
+    free( ps_cipher_text );
+    gcry_mpi_release( cipher_text_mpi );
+    gcry_sexp_release( cipher_text_sexp );
+    gcry_sexp_release( plain_text_sexp );
+    gcry_mpi_release( plain_text_mpi );
+    gcry_sexp_release( tmp_sexp );
+    gcry_free( ps_plain_text );
+    return i_ret;
+}
+
+/* extracts and saves the AES key info from the plaintext;
+ * parameter smpte is true for SMPTE DCP, false for Interop;
+ * see SMPTE 430-1-2006, section 6.1.2 for the exact structure of the plaintext
+ */
+int AESKey::extractInfo( unsigned char * ps_plain_text, bool smpte )
+{
+
+    string s_rsa_structID( "f1dc124460169a0e85bc300642f866ab" ); /* unique Structure ID for all RSA-encrypted AES keys in a KDM */
+    string s_carrier;
+    char psz_hex[3];
+    int i_ret, i_pos = 0;
+
+    /* check for the structure ID */
+    while( i_pos < 16 )
+    {
+        i_ret = snprintf( psz_hex, 3, "%02hhx", ps_plain_text[i_pos] );
+        if( i_ret != 2 )
+        {
+            msg_Err( this->p_demux, "error while extracting structure ID from decrypted cipher" );
+            return VLC_EGENERIC;
+        }
+        try
+        {
+            s_carrier.append( psz_hex );
+        }
+        catch( ... )
+        {
+            msg_Err( this->p_demux, "error while handling string" );
+            return VLC_EGENERIC;
+        }
+        i_pos++;
+    }
+    if( s_carrier.compare( s_rsa_structID ) )
+    {
+        msg_Err( this->p_demux, "incorrect RSA structure ID: KDM may be broken" );
+        return VLC_EGENERIC;
+    }
+
+    i_pos += 36;        /* TODO thumbprint, CPL ID */
+    if( smpte )         /* only SMPTE DCPs have the 4-byte "KeyType" field */
+        i_pos += 4;
+
+    /* extract the AES key UUID */
+    if( ( this->s_key_id = createUUID( ps_plain_text + i_pos ) ).empty() )
+    {
+        msg_Err( this->p_demux, "error while extracting AES Key UUID" );
+        return VLC_EGENERIC;
+    }
+    i_pos += 16;
+
+    i_pos += 50; /* TODO KeyEpoch */
+
+    /* extract the AES key */
+    memcpy( this->ps_key, ps_plain_text + i_pos, 16 );
+
+    return VLC_SUCCESS;
+}
+
+
+/*
+ * RSAKey class
+ */
+
+/*
+ * gets the private key path (always stored in the VLC config dir and called "priv.key" )
+ */
+int RSAKey::setPath( )
+{
+    char *psz_config_dir = NULL;
+
+    if( ! ( psz_config_dir = config_GetUserDir( VLC_CONFIG_DIR ) ) )
+    {
+        msg_Err( this->p_demux, "could not read user config dir" );
+        goto error;
+    }
+    try
+    {
+        this->s_path.assign( psz_config_dir );
+        this->s_path.append( "/priv.key" );
+    }
+    catch( ... )
+    {
+        msg_Err( this->p_demux, "error while handling string" );
+        goto error;
+    }
+
+    free( psz_config_dir );
+    return VLC_SUCCESS;
+
+error:
+    free( psz_config_dir );
+    return VLC_EGENERIC;
+}
+
+/*
+ * reads the RSA private key from file
+ * the file must be conform to PCKS#1, PEM-encoded, unencrypted
+ */
+int RSAKey::readPEM( )
+{
+    string s_header_tag( "-----BEGIN RSA PRIVATE KEY-----" );
+    string s_footer_tag( "-----END RSA PRIVATE KEY-----" );
+    string s_line;
+    string s_data_b64;
+    unsigned char *ps_data_der = NULL;
+    size_t length;
+
+    /* open key file */
+    ifstream file( this->s_path.c_str(), ios::in );
+    if( ! file.is_open() )
+    {
+        msg_Err( this->p_demux, "could not open private key file" );
+        goto error;
+    }
+
+    /* check for header tag */
+    if( ! getline( file, s_line ) )
+    {
+        msg_Err( this->p_demux, "could not read private key file" );
+        goto error;
+    }
+    if( s_line.compare( s_header_tag ) )
+    {
+        msg_Err( this->p_demux, "unexpected header tag found in private key file" );
+        goto error;
+    }
+
+    /* read file until footer tag is found */
+    while( getline( file, s_line ) )
+    {
+        if( ! s_line.compare( s_footer_tag ) )
+            break;
+        try
+        {
+            s_data_b64.append( s_line );
+        }
+        catch( ... )
+        {
+            msg_Err( this->p_demux, "error while handling string" );
+            goto error;
+        }
+    }
+    if( ! file )
+    {
+        msg_Err( this->p_demux, "error while reading private key file; footer tag may be missing" );
+        goto error;
+    }
+
+    /* decode data from Base64 */
+    if( ! ( length = vlc_b64_decode_binary( &ps_data_der, s_data_b64.c_str() ) ) )
+    {
+        msg_Err( this->p_demux, "could not decode from Base64" );
+        goto error;
+    }
+
+    /* extract key S-expression from DER-encoded data */
+    if( this->readDER( ps_data_der, length ) )
+        goto error;
+
+    /* clear data */
+    free( ps_data_der );
+    return VLC_SUCCESS;
+
+error:
+    free( ps_data_der );
+    return VLC_EGENERIC;
+}
+
+/*
+ * Parse the DER-encoded data at ps_data_der
+ * saving the key in an S-expression
+ */
+int RSAKey::readDER( unsigned char const* ps_data_der, size_t length )
+{
+    struct tag_info tag_inf;
+    gcry_mpi_t key_params[8];
+    gcry_error_t err;
+    int i;
+
+    /* parse the ASN1 structure */
+    if( parseTag( &ps_data_der, &length, &tag_inf )
+            || tag_inf.tag != TAG_SEQUENCE || tag_inf.class_ || !tag_inf.cons || tag_inf.ndef )
+        goto bad_asn1;
+    if( parseTag( &ps_data_der, &length, &tag_inf )
+       || tag_inf.tag != TAG_INTEGER || tag_inf.class_ || tag_inf.cons || tag_inf.ndef )
+        goto bad_asn1;
+    if( tag_inf.length != 1 || *ps_data_der )
+        goto bad_asn1;  /* The value of the first integer is no 0. */
+    ps_data_der += tag_inf.length;
+    length -= tag_inf.length;
+
+    for( i = 0; i < 8; i++ )
+    {
+        if( parseTag( &ps_data_der, &length, &tag_inf )
+                || tag_inf.tag != TAG_INTEGER || tag_inf.class_ || tag_inf.cons || tag_inf.ndef )
+            goto bad_asn1;
+        err = gcry_mpi_scan( key_params + i, GCRYMPI_FMT_USG, ps_data_der, tag_inf.length, NULL );
+        if( err )
+        {
+            msg_Err( this->p_demux, "error scanning RSA parameter %d: %s", i, gpg_strerror( err ) );
+            goto error;
+        }
+        ps_data_der += tag_inf.length;
+        length -= tag_inf.length;
+    }
+
+    /* Convert from OpenSSL parameter ordering to the OpenPGP order.
+     * First check that p < q; if not swap p and q and recompute u.
+     */
+    if( gcry_mpi_cmp( key_params[3], key_params[4] ) > 0 )
+    {
+        gcry_mpi_swap( key_params[3], key_params[4] );
+        gcry_mpi_invm( key_params[7], key_params[3], key_params[4] );
+    }
+
+    /* Build the S-expression.  */
+    err = gcry_sexp_build( & this->priv_key, NULL,
+                         "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
+                         key_params[0], key_params[1], key_params[2],
+                         key_params[3], key_params[4], key_params[7] );
+    if( err )
+    {
+        msg_Err( this->p_demux, "error building S-expression: %s", gpg_strerror( err ) );
+        goto error;
+    }
+
+    /* clear data */
+    for( i = 0; i < 8; i++ )
+        gcry_mpi_release( key_params[i] );
+    return VLC_SUCCESS;
+
+bad_asn1:
+    msg_Err( this->p_demux, "could not parse ASN1 structure; key might be corrupted" );
+
+error:
+    for( i = 0; i < 8; i++ )
+        gcry_mpi_release( key_params[i] );
+    return VLC_EGENERIC;
+}
+
+/*
+ * Parse the buffer at the address BUFFER which consists of the number
+ * of octets as stored at BUFLEN.  Return the tag and the length part
+ * from the TLV triplet.  Update BUFFER and BUFLEN on success.  Checks
+ * that the encoded length does not exhaust the length of the provided
+ * buffer.
+ */
+int RSAKey::parseTag( unsigned char const **buffer, size_t *buflen, struct tag_info *ti)
+{
+  int c;
+  unsigned long tag;
+  const unsigned char *buf = *buffer;
+  size_t length = *buflen;
+
+  ti->length = 0;
+  ti->ndef = 0;
+  ti->nhdr = 0;
+
+  /* Get the tag */
+  if (!length)
+    return -1; /* Premature EOF.  */
+  c = *buf++; length--;
+  ti->nhdr++;
+
+  ti->class_ = (c & 0xc0) >> 6;
+  ti->cons   = !!(c & 0x20);
+  tag        = (c & 0x1f);
+
+  if (tag == 0x1f)
+    {
+      tag = 0;
+      do
+        {
+          tag <<= 7;
+          if (!length)
+            return -1; /* Premature EOF.  */
+          c = *buf++; length--;
+          ti->nhdr++;
+          tag |= (c & 0x7f);
+        }
+      while ( (c & 0x80) );
+    }
+  ti->tag = tag;
+
+  /* Get the length */
+  if (!length)
+    return -1; /* Premature EOF. */
+  c = *buf++; length--;
+  ti->nhdr++;
+
+  if ( !(c & 0x80) )
+    ti->length = c;
+  else if (c == 0x80)
+    ti->ndef = 1;
+  else if (c == 0xff)
+    return -1; /* Forbidden length value.  */
+  else
+    {
+      unsigned long len = 0;
+      int count = c & 0x7f;
+
+      for (; count; count--)
+        {
+          len <<= 8;
+          if (!length)
+            return -1; /* Premature EOF.  */
+          c = *buf++; length--;
+          ti->nhdr++;
+          len |= (c & 0xff);
+        }
+      ti->length = len;
+    }
+
+  if (ti->class_ == 0 && !ti->tag)
+    ti->length = 0;
+
+  if (ti->length > length)
+    return -1; /* Data larger than buffer.  */
+
+  *buffer = buf;
+  *buflen = length;
+  return 0;
+}
index cd42922..a738fc9 100644 (file)
@@ -9,6 +9,7 @@
  *          Anthony Giniers
  *          Ludovic Hoareau
  *          Loukmane Dessai
+ *          Simona-Marinela Prodea <simona dot marinela dot prodea at gmail dot com>
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as published by
 
 using namespace std;
 
-static int ReadNextNode(xml_reader_t *p_xmlReader, string& p_node) {
-    const char * c_node;
-    int i;
-    i = xml_ReaderNextNode( p_xmlReader, &c_node );
-    p_node = c_node;
-    return i;
-}
-
-static int ReadEndNode( xml_reader_t *p_xmlReader, string p_node,
-                        int p_type, string &s_value) {
-    string node;
-
-    if ( xml_ReaderIsEmptyElement( p_xmlReader) )
-            return 0;
-
-    if (p_type != XML_READER_STARTELEM)
-        return -1;
-
-    if (ReadNextNode(p_xmlReader, node) == XML_READER_TEXT)
-    {
-        s_value = node;
-        if((ReadNextNode(p_xmlReader, node) == XML_READER_ENDELEM) &&
-                node == p_node)
-            return 0;
-    }
-    return -1;
-}
-
 typedef enum {
     CHUNK_UNKNOWN = 0,
     CHUNK_PATH,
@@ -161,7 +134,7 @@ int Chunk::Parse( xml_reader_t *p_xmlReader, string p_node, int p_type){
     if( p_node != "Chunk")
         return -1;
     /* loop on Chunks Node */
-    while( ( type = ReadNextNode( p_xmlReader, node ) ) > 0 ) {
+    while( ( type = XmlFile::ReadNextNode( p_xmlReader, node ) ) > 0 ) {
         switch (type) {
             case XML_READER_STARTELEM:
             {
@@ -169,7 +142,7 @@ int Chunk::Parse( xml_reader_t *p_xmlReader, string p_node, int p_type){
                 for(ChunkTag_t i = CHUNK_PATH; i <= CHUNK_LENGTH; i = ChunkTag_t(i+1)) {
                     if( node == names[i-1]) {
                         chunk_tag = i;
-                        if ( ReadEndNode(p_xmlReader, node, type, s_value))
+                        if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value))
                             return -1;
                         switch (chunk_tag) {
                             case CHUNK_PATH:
@@ -230,11 +203,13 @@ AssetMap::~AssetMap() { }
 int AssetMap::Parse ( )
 {
     int type = 0;
+    int retval;
     int reel_nbr = 0;
     int index = 0;
     int sum_duration_vid = 0;
     int sum_duration_aud = 0;
     string node;
+    char *psz_kdm_path;
 
     CPL  *cpl;
     Reel *reel;
@@ -251,7 +226,7 @@ int AssetMap::Parse ( )
 
     /* reading ASSETMAP file to get the asset_list */
     msg_Dbg( p_demux, "reading ASSETMAP file..." );
-    while( ( type = ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
+    while( ( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
         if ( (type == XML_READER_STARTELEM) && ( node =="AssetList")) {
             _p_asset_list =  new (nothrow) AssetList();
             if ( unlikely(_p_asset_list == NULL) ) {
@@ -337,6 +312,29 @@ int AssetMap::Parse ( )
         return -1;
     }
 
+    /* KDM, if needed */
+    for( AssetList::iterator iter = _p_asset_list->begin(); iter != _p_asset_list->end(); ++iter )
+        if( ! (*iter)->getKeyId().empty() )
+        {
+            msg_Dbg( p_demux, "DCP is encrypted, searching KDM file...");
+            psz_kdm_path = var_InheritString( p_demux, "kdm" );
+            if( !psz_kdm_path || !*psz_kdm_path )
+            {
+                msg_Err( p_demux, "cryptographic key IDs found in CPL and no path to KDM given");
+                free( psz_kdm_path );
+                this->CloseXml();
+                return VLC_EGENERIC;
+            }
+            KDM p_kdm( p_demux, psz_kdm_path, p_dcp );
+            free( psz_kdm_path );
+            if( ( retval = p_kdm.Parse() ) )
+            {
+                this->CloseXml();
+                return retval;
+            }
+            break;
+        }
+
     reel_nbr = cpl->getReelList().size();
     for(index = 0; index != reel_nbr; ++index)
     {
@@ -356,6 +354,7 @@ int AssetMap::Parse ( )
             video.i_duration = asset->getDuration();
             video.i_correction = video.i_entrypoint - sum_duration_vid + video.i_duration;
             video.i_absolute_end = sum_duration_vid;
+            video.p_key = asset->getAESKeyById( p_dcp->p_key_list, asset->getKeyId() );
             p_dcp->video_reels.push_back(video);
             msg_Dbg( this->p_demux, "Video Track: %s",asset->getPath().c_str());
             msg_Dbg( this->p_demux, "Entry point: %i",asset->getEntryPoint());
@@ -378,6 +377,7 @@ int AssetMap::Parse ( )
             audio.i_duration = asset->getDuration();
             audio.i_correction = audio.i_entrypoint - sum_duration_aud + audio.i_duration;
             audio.i_absolute_end = sum_duration_aud;
+            audio.p_key = asset->getAESKeyById( p_dcp->p_key_list, asset->getKeyId() );
             p_dcp->audio_reels.push_back(audio);
             msg_Dbg( this->p_demux, "Audio Track: %s",asset->getPath().c_str());
             msg_Dbg( this->p_demux, "Entry point: %i",asset->getEntryPoint());
@@ -407,7 +407,7 @@ int Asset::Parse( xml_reader_t *p_xmlReader, string p_node, int p_type)
     if( p_node != s_root_node)
         return -1;
     /* loop on Assets Node */
-    while( ( type = ReadNextNode( p_xmlReader, node ) ) > 0 ) {
+    while( ( type = XmlFile::ReadNextNode( p_xmlReader, node ) ) > 0 ) {
         switch (type) {
             case XML_READER_STARTELEM:
                 {
@@ -421,7 +421,7 @@ int Asset::Parse( xml_reader_t *p_xmlReader, string p_node, int p_type)
                                     /* case of <PackinkList/> tag, bur not compliant with SMPTE-429-9 2007*/
                                     if (xml_ReaderIsEmptyElement( p_xmlReader))
                                         this->b_is_packing_list = true;
-                                    else if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                    else if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                         return -1;
                                     if ( s_value == "true" )
                                         this->b_is_packing_list = true;
@@ -432,12 +432,12 @@ int Asset::Parse( xml_reader_t *p_xmlReader, string p_node, int p_type)
                                     this->s_path = this->chunk_vec[0].getPath();
                                     break;
                                 case ASSET_ID:
-                                    if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                    if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                         return -1;
                                     this->s_id = s_value;
                                     break;
                                 case ASSET_ANNOTATION_TEXT:
-                                    if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                    if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                         return -1;
                                     this->s_annotation = s_value;
                                     break;
@@ -493,7 +493,7 @@ int Asset::ParsePKL( xml_reader_t *p_xmlReader)
     string s_value;
     const string s_root_node = "Asset";
 
-    while( ( type = ReadNextNode( p_xmlReader, node ) ) > 0 ) {
+    while( ( type = XmlFile::ReadNextNode( p_xmlReader, node ) ) > 0 ) {
         switch (type) {
             case XML_READER_STARTELEM:
                 {
@@ -503,7 +503,7 @@ int Asset::ParsePKL( xml_reader_t *p_xmlReader)
                             _tag = i;
                             switch(_tag) {
                                 case ASSET_ANNOTATION_TEXT:
-                                    if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                    if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                         return -1;
                                     if ( this->s_annotation.empty() )
                                         this->s_annotation = s_value;
@@ -511,22 +511,22 @@ int Asset::ParsePKL( xml_reader_t *p_xmlReader)
                                         this->s_annotation = this->s_annotation + "--" + s_value;
                                     break;
                                 case ASSET_HASH:
-                                    if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                    if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                         return -1;
                                     this->s_hash = s_value;
                                     break;
                                 case ASSET_SIZE:
-                                    if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                    if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                         return -1;
                                     this->ui_size = atol(s_value.c_str());
                                     break;
                                 case ASSET_TYPE:
-                                    if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                    if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                         return -1;
                                     this->s_type = s_value;
                                     break;
                                 case ASSET_ORIGINAL_FILENAME:
-                                    if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                    if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                         return -1;
                                     this->s_original_filename = s_value;
                                     break;
@@ -602,7 +602,7 @@ int Asset::parseChunkList( xml_reader_t *p_xmlReader, string p_node, int p_type)
     if( p_node != "ChunkList" )
         return -1;
     /* loop on Assets Node */
-    while( ( type = ReadNextNode( p_xmlReader, node ) ) > 0 ) {
+    while( ( type = XmlFile::ReadNextNode( p_xmlReader, node ) ) > 0 ) {
          switch (type) {
             case XML_READER_STARTELEM:
                 {
@@ -629,6 +629,20 @@ int Asset::parseChunkList( xml_reader_t *p_xmlReader, string p_node, int p_type)
     return -1;
 }
 
+AESKey * Asset::getAESKeyById( AESKeyList* p_key_list, const string s_id )
+{
+    /* return NULL if DCP is not encrypted */
+    if( !p_key_list || s_id.empty() )
+        return NULL;
+
+    for( AESKeyList::iterator index = p_key_list->begin(); index != p_key_list->end(); ++index )
+        if( (*index)->getKeyId() == s_id )
+            return *index;
+
+    return NULL;
+}
+
+
 int AssetMap::ParseAssetList (xml_reader_t *p_xmlReader, const string p_node, int p_type)
 {
     string node;
@@ -640,7 +654,7 @@ int AssetMap::ParseAssetList (xml_reader_t *p_xmlReader, const string p_node, in
     if( p_node != "AssetList" )
         return -1;
     /* loop on AssetList nodes */
-    while( ( type = ReadNextNode( p_xmlReader, node ) ) > 0 ) {
+    while( ( type = XmlFile::ReadNextNode( p_xmlReader, node ) ) > 0 ) {
         switch (type) {
             case XML_READER_STARTELEM:
                 if (node != "Asset" )
@@ -707,6 +721,35 @@ int XmlFile::OpenXml()
     return 0;
 }
 
+int XmlFile::ReadNextNode( xml_reader_t *p_xmlReader, string& p_node )
+{
+    const char * c_node;
+    int i;
+    i = xml_ReaderNextNode( p_xmlReader, &c_node );
+    p_node = c_node;
+    return i;
+}
+
+int XmlFile::ReadEndNode( xml_reader_t *p_xmlReader, string p_node, int p_type, string &s_value)
+{
+    string node;
+
+    if ( xml_ReaderIsEmptyElement( p_xmlReader) )
+            return 0;
+
+    if (p_type != XML_READER_STARTELEM)
+        return -1;
+
+    if ( XmlFile::ReadNextNode( p_xmlReader, node ) == XML_READER_TEXT )
+    {
+        s_value = node;
+        if( ( XmlFile::ReadNextNode( p_xmlReader, node ) == XML_READER_ENDELEM ) &&
+                node == p_node)
+            return 0;
+    }
+    return -1;
+}
+
 void XmlFile::CloseXml() {
     if( this->p_stream )
         stream_Delete( this->p_stream );
@@ -756,12 +799,12 @@ int PKL::Parse()
         return -1;
 
     /* read 1st node  and verify that is a PKL*/
-    if (! ( ( XML_READER_STARTELEM == ReadNextNode(this->p_xmlReader, node) ) &&
+    if (! ( ( XML_READER_STARTELEM == XmlFile::ReadNextNode(this->p_xmlReader, node) ) &&
                 (node == s_root_node) ) ) {
         msg_Err( this->p_demux, "Not a valid XML Packing List");
         goto error;
     }
-    while( ( type = ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
+    while( ( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
         switch (type) {
             case XML_READER_STARTELEM: {
                 PKLTag_t _tag = PKL_UNKNOWN;
@@ -784,37 +827,37 @@ int PKL::Parse()
                                 break;
                             /* Parse simple/end nodes */
                             case PKL_ID:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_id = s_value;
                                 break;
                             case PKL_ISSUE_DATE:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_issue_date = s_value;
                                 break;
                             case PKL_ISSUER:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_issuer = s_value;
                                 break;
                             case PKL_CREATOR:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_creator = s_value;
                                 break;
                             case PKL_ANNOTATION_TEXT:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_annotation = s_value;
                                 break;
                             case PKL_ICON_ID:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_icon_id = s_value;
                                 break;
                             case PKL_GROUP_ID:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_group_id = s_value;
                                 break;
@@ -890,7 +933,7 @@ int PKL::ParseAssetList(string p_node, int p_type) {
         return -1;
     if( p_node != "AssetList")
         return -1;
-    while( ( type = ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
+    while( ( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
         switch (type) {
             case XML_READER_STARTELEM:
                 if( node =="Asset") {
@@ -923,16 +966,16 @@ int PKL::ParseAsset(string p_node, int p_type) {
         return -1;
 
     /* 1st node shall be Id" */
-    if (( type = ReadNextNode( this->p_xmlReader, node ) ) > 0)
+    if (( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0)
         if ( ! ((type == XML_READER_STARTELEM) && (node == "Id")))
             return -1;
-    if (( type = ReadNextNode( this->p_xmlReader, node ) ) > 0)
+    if (( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0)
          if (type == XML_READER_TEXT) {
             s_value = node;
             if (unlikely(node.empty()))
                 return -1;
             }
-    if (( type = ReadNextNode( this->p_xmlReader, node ) ) > 0)
+    if (( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0)
         if (type == XML_READER_ENDELEM) {
             asset = AssetMap::getAssetById(this->asset_list, s_value);
             if (asset  == NULL)
@@ -955,7 +998,7 @@ int PKL::ParseSigner(string p_node, int p_type)
     if( p_node != "Signer")
         return -1;
 
-    while( ( type = ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
+    while( ( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
         /* TODO not implemented. Just parse until end of Signer node */
             if ((node == p_node) && (type = XML_READER_ENDELEM))
                 return 0;
@@ -975,7 +1018,7 @@ int PKL::ParseSignature(string p_node, int p_type)
     if( p_node != "ds:Signature")
         return -1;
 
-    while (( type = ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
+    while (( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
         /* TODO not implemented. Just parse until end of Signature node */
             if ((node == p_node) && (type = XML_READER_ENDELEM))
                 return 0;
@@ -997,15 +1040,15 @@ int Reel::Parse(string p_node, int p_type) {
     if( p_node != "Reel")
         return -1;
 
-    while (( type = ReadNextNode(this->p_xmlReader, node ) ) > 0 ) {
+    while (( type = XmlFile::ReadNextNode(this->p_xmlReader, node ) ) > 0 ) {
         switch (type) {
             case XML_READER_STARTELEM:
                 if (node =="Id") {
-                    if ( ReadEndNode(this->p_xmlReader, node, type, s_value))
+                    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value))
                         return -1;
                     this->s_id = s_value;
                 } else if (node == "AnnotationText") {
-                    if ( ReadEndNode(this->p_xmlReader, node, type, s_value))
+                    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value))
                         return -1;
                     this->s_annotation = s_value;
                 } else if ( node =="AssetList" ) {
@@ -1060,7 +1103,7 @@ int Reel::ParseAssetList(string p_node, int p_type) {
     if( p_node != "AssetList")
         return -1;
 
-    while (( type = ReadNextNode( this->p_xmlReader, node ) ) > 0 )  {
+    while (( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0 )  {
         switch (type) {
             case XML_READER_STARTELEM:
                 if (node =="MainPicture") {
@@ -1105,11 +1148,11 @@ int Reel::ParseAsset(string p_node, int p_type, TrackType_t e_track) {
         return -1;
 
     /* 1st node shall be Id */
-    if (( type = ReadNextNode( this->p_xmlReader, node ) ) > 0)
+    if (( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0)
         if ( ! ((type == XML_READER_STARTELEM) && (node == "Id")))
             return -1;
 
-    if ( ReadEndNode(this->p_xmlReader, node, type, s_value) )
+    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value) )
         return -1;
 
     asset = AssetMap::getAssetById(this->p_asset_list, s_value);
@@ -1117,42 +1160,43 @@ int Reel::ParseAsset(string p_node, int p_type, TrackType_t e_track) {
         return -1;
 
     while(  (! b_stop_parse) &&
-            (( type = ReadNextNode( this->p_xmlReader, node ) ) > 0 ) ) {
+            (( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0 ) ) {
         switch (type) {
             case XML_READER_STARTELEM:
                 if (node =="EditRate") {
-                    if ( ReadEndNode(this->p_xmlReader, node, type, s_value))
+                    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value))
                         return -1;
                 } else if (node == "AnnotationText") {
-                    if ( ReadEndNode(this->p_xmlReader, node, type, s_value))
+                    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value))
                         return -1;
                         asset->setAnnotation(s_value);
                 } else if (node == "IntrinsicDuration") {
-                    if ( ReadEndNode(this->p_xmlReader, node, type, s_value))
+                    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value))
                         return -1;
                         asset->setIntrinsicDuration(atoi(s_value.c_str()));
                 } else if (node == "EntryPoint") {
-                    if ( ReadEndNode(this->p_xmlReader, node, type, s_value))
+                    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value))
                         return -1;
                         asset->setEntryPoint(atoi(s_value.c_str()));
                 } else if (node == "Duration") {
-                    if ( ReadEndNode(this->p_xmlReader, node, type, s_value))
+                    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value))
                         return -1;
                         asset->setDuration(atoi(s_value.c_str()));
                 } else if (node == "KeyId") {
-                    if ( ReadEndNode(this->p_xmlReader, node, type, s_value))
+                    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value))
                         return -1;
+                    asset->setKeyId( s_value );
                 } else if (node == "Hash") {
-                    if ( ReadEndNode(this->p_xmlReader, node, type, s_value))
+                    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value))
                         return -1;
                 } else if (node == "FrameRate") {
-                    if ( ReadEndNode(this->p_xmlReader, node, type, s_value))
+                    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value))
                         return -1;
                 } else if (node == "ScreenAspectRatio") {
-                    if ( ReadEndNode(this->p_xmlReader, node, type, s_value))
+                    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value))
                         return -1;
                 } else if (node == "Language") {
-                    if ( ReadEndNode(this->p_xmlReader, node, type, s_value))
+                    if ( XmlFile::ReadEndNode(this->p_xmlReader, node, type, s_value))
                         return -1;
                 } else {
                     /* unknown tag */
@@ -1208,7 +1252,7 @@ CPL::CPL(demux_t * p_demux, string s_path, AssetList *_asset_list)
         }
 
     /* read 1st node  and verify that is a CPL */
-    if ( (type = ReadNextNode(p_xmlReader, node)) > 0) {
+    if ( (type = XmlFile::ReadNextNode(p_xmlReader, node)) > 0) {
         if ( (type == XML_READER_STARTELEM) && (node == "CompositionPlaylist") ) {
             this->type = XML_CPL;
         }
@@ -1248,13 +1292,13 @@ int CPL::Parse()
         return -1;
 
     /* read 1st node  and verify that is a CPL*/
-    if (! ( ( XML_READER_STARTELEM == ReadNextNode(this->p_xmlReader, node) ) &&
+    if (! ( ( XML_READER_STARTELEM == XmlFile::ReadNextNode(this->p_xmlReader, node) ) &&
                 (node == s_root_node) ) ) {
         msg_Err( this->p_demux, "Not a valid XML Packing List");
         goto error;
     }
 
-    while( ( type = ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
+    while( ( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
         switch (type) {
             case XML_READER_STARTELEM: {
                 CPLTag_t _tag = CPL_UNKNOWN;
@@ -1276,42 +1320,42 @@ int CPL::Parse()
                                 break;
                                 /* Parse simple/end nodes */
                             case CPL_ID:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_id = s_value;
                                 break;
                             case CPL_ANNOTATION_TEXT:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_annotation = s_value;
                                 break;
                             case CPL_ICON_ID:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_icon_id = s_value;
                                 break;
                             case CPL_ISSUE_DATE:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_issue_date= s_value;
                                 break;
                             case CPL_ISSUER:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_issuer = s_value;
                                 break;
                             case CPL_CREATOR:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_creator = s_value;
                                 break;
                             case CPL_CONTENT_TITLE:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_content_title = s_value;
                                 break;
                             case CPL_CONTENT_KIND:
-                                if ( ReadEndNode(p_xmlReader, node, type, s_value) )
+                                if ( XmlFile::ReadEndNode(p_xmlReader, node, type, s_value) )
                                     goto error;
                                 this->s_content_kind = s_value;
                                 break;
@@ -1358,7 +1402,7 @@ int CPL::ParseReelList(string p_node, int p_type) {
         return -1;
     if( p_node != "ReelList")
         return -1;
-    while( ( type = ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
+    while( ( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
         switch (type) {
             case XML_READER_STARTELEM: {
                 Reel *p_reel = new (nothrow) Reel( this->p_demux, this->asset_list, this->p_xmlReader);
@@ -1401,7 +1445,7 @@ int CPL::DummyParse(string p_node, int p_type)
     if (xml_ReaderIsEmptyElement( this->p_xmlReader))
         return 0;
 
-    while (( type = ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
+    while (( type = XmlFile::ReadNextNode( this->p_xmlReader, node ) ) > 0 ) {
         /* TODO not implemented. Just pase until end of input node */
         if ((node == p_node) && (type = XML_READER_ENDELEM))
             return 0;
index 32059f5..300c9da 100644 (file)
@@ -8,6 +8,7 @@
  *          Anthony Giniers
  *          Ludovic Hoareau
  *          Loukmane Dessai
+ *          Simona-Marinela Prodea <simona dot marinela dot prodea at gmail dot com>
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as published by
 #include <vlc_demux.h>
 #include <vlc_plugin.h>
 
+/* gcrypt headers */
+#include <gcrypt.h>
+#include <vlc_gcrypt.h>
+
 #include <iostream>
 #include <string>
 #include <list>
@@ -67,7 +72,8 @@ typedef enum {
 class Asset;
 class AssetList: public std::list<Asset *> {};
 class PKL;
-
+class AESKey;
+class AESKeyList: public std::list<AESKey *> {};
 
 /* This struct stores useful information about an MXF for demux() */
 struct info_reel
@@ -77,6 +83,7 @@ struct info_reel
     int i_duration;
     int i_correction;       /* entrypoint - sum of previous durations */
     uint32_t i_absolute_end;     /* correction + duration */
+    AESKey * p_key;
 };
 
 /* This struct stores the most important information about the DCP */
@@ -86,12 +93,13 @@ struct dcp_t
 
     vector<PKL *> pkls;
     AssetList *p_asset_list;
+    AESKeyList *p_key_list;
 
     vector<info_reel> audio_reels;
     vector<info_reel> video_reels;
 
     dcp_t():
-        p_asset_list(NULL) {};
+        p_asset_list(NULL), p_key_list(NULL) {};
 
     ~dcp_t( ) {
         vlc_delete_all(pkls);
@@ -100,6 +108,10 @@ struct dcp_t
             delete(p_asset_list);
 
         }
+        if ( p_key_list != NULL ) {
+            vlc_delete_all(*p_key_list);
+            delete(p_key_list);
+        }
     }
 };
 
@@ -117,6 +129,9 @@ public:
 
     virtual int Parse() = 0;
 
+    static int ReadNextNode( xml_reader_t *p_xmlReader, string& s_node );
+    static int ReadEndNode( xml_reader_t *p_xmlReader, string s_node, int i_type, string &s_value );
+
     bool IsCPL() { return type == XML_CPL; }
 protected:
     demux_t      *p_demux;
@@ -164,6 +179,7 @@ public:
         else
             this->s_annotation = this->s_annotation + "--" + p_string;
     };
+    void setKeyId(string p_string) { this->s_key_id = p_string; };
     void setPackingList(bool p_bool) { this->s_path = p_bool; };
     void setEntryPoint(int i_val) { this->i_entry_point = i_val; };
     void setDuration (int i_val) { this->i_duration = i_val; };
@@ -172,6 +188,7 @@ public:
     string getPath() const { return this->s_path; };
     string getType() const { return this->s_type; };
     string getOriginalFilename() const { return this->s_original_filename; };
+    string getKeyId() const { return this->s_key_id; }
     int getEntryPoint() const { return this->i_entry_point; };
     int getDuration() const { return this->i_duration; };
     int getIntrinsicDuration() const { return this->i_intrisic_duration; };
@@ -181,6 +198,8 @@ public:
     int Parse( xml_reader_t *p_xmlReader, string node, int type);
     int ParsePKL( xml_reader_t *p_xmlReader);
 
+    static AESKey * getAESKeyById( AESKeyList* , const string s_id );
+
     // TODO: remove
     void Dump();
 
@@ -315,4 +334,76 @@ private:
 
     int ParseAssetList (xml_reader_t *p_xmlReader, const string p_node, int p_type);
 };
+
+class KDM : public XmlFile {
+
+public:
+    KDM( demux_t * p_demux, string s_path, dcp_t *_p_dcp )
+        : XmlFile( p_demux, s_path ), p_dcp(_p_dcp) {}
+
+    virtual int Parse();
+
+private:
+    dcp_t *p_dcp;
+
+    int ParsePrivate( const string s_node, int i_type );
+};
+
+class AESKey
+{
+public:
+    AESKey( demux_t *demux ): p_demux( demux ) { }
+    virtual ~AESKey() {};
+
+    const string getKeyId() { return this->s_key_id; };
+    const unsigned char * getKey() { return this->ps_key; };
+
+    int Parse( xml_reader_t *p_xml_reader, string s_node, int i_type );
+
+private:
+    demux_t *p_demux;
+    string s_key_id;
+    unsigned char ps_key[16];
+
+    int decryptRSA( string s_cipher_text_b64 );
+    int extractInfo( unsigned char * ps_plain_text, bool smpte );
+};
+
+class RSAKey
+{
+public:
+    RSAKey( demux_t *demux ):
+        priv_key( NULL ), p_demux( demux ) { }
+    virtual ~RSAKey() { gcry_sexp_release( priv_key ); }
+
+    /* some ASN.1 tags.  */
+    enum
+    {
+      TAG_INTEGER = 2,
+      TAG_SEQUENCE = 16,
+    };
+
+    /* ASN.1 Parser object.  */
+    struct tag_info
+    {
+        int class_;            /* Object class.  */
+        unsigned long tag;     /* The tag of the object.  */
+        unsigned long length;  /* Length of the values.  */
+        int nhdr;              /* Length of the header (TL).  */
+        unsigned int ndef:1;   /* The object has an indefinite length.  */
+        unsigned int cons:1;   /* This is a constructed object.  */
+    };
+
+    int setPath();
+    int readPEM();
+    int readDER( unsigned char const *ps_data_der, size_t length );
+    int parseTag( unsigned char const **buffer, size_t *buflen, struct tag_info *ti);
+
+    gcry_sexp_t priv_key;
+
+private:
+    demux_t *p_demux;
+    string s_path;
+};
+
 #endif /* _DCPPARSER_H */