[vlc-devel] [PATCHv2] dcp: read encrypted DCPs
Simona-Marinela Prodea
simona.marinela.prodea at gmail.com
Fri Apr 18 16:40:01 CEST 2014
Allow read of encrypted DCP with KDM, using libgcrypt.
---
modules/access/Makefile.am | 6 +-
modules/access/dcp/dcp.cpp | 31 +-
modules/access/dcp/dcpparser.cpp | 688 ++++++++++++++++++++++++++++++++++++++
modules/access/dcp/dcpparser.h | 98 +++++-
4 files changed, 814 insertions(+), 9 deletions(-)
diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am
index a5b3679..f66f7bc 100644
--- a/modules/access/Makefile.am
+++ b/modules/access/Makefile.am
@@ -11,10 +11,14 @@ access_LTLIBRARIES += libattachment_plugin.la
libdcp_plugin_la_SOURCES = access/dcp/dcpparser.h access/dcp/dcp.cpp access/dcp/dcpparser.cpp
if HAVE_ASDCP
-libdcp_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(ASDCP_CFLAGS)
+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)
diff --git a/modules/access/dcp/dcp.cpp b/modules/access/dcp/dcp.cpp
index 7960c6e..b372cdf 100644
--- a/modules/access/dcp/dcp.cpp
+++ b/modules/access/dcp/dcp.cpp
@@ -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>
@@ -50,9 +52,6 @@
#include <vlc_url.h>
#include <vlc_aout.h>
-/* ASDCP headers */
-#include <AS_DCP.h>
-
#include <vector>
#include "dcpparser.h"
@@ -72,6 +71,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 +604,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 +633,14 @@ static int Demux( demux_t *p_demux )
}
/* video frame */
+
+ /* initialize AES context, if reel is encrypted */
+ if( 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 +655,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 +679,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 +699,21 @@ 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_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/dcpparser.cpp b/modules/access/dcp/dcpparser.cpp
index cd42922..371bc63 100644
--- a/modules/access/dcp/dcpparser.cpp
+++ b/modules/access/dcp/dcpparser.cpp
@@ -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
@@ -28,6 +29,10 @@
/**
* @file dcpparser.cpp
* @brief Parsing of DCP XML files
+ *
+ * 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
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
@@ -38,11 +43,15 @@
#include <vlc_plugin.h>
#include <vlc_xml.h>
#include <vlc_url.h>
+#include <vlc_fs.h>
+#include <vlc_strings.h>
#include <iostream>
#include <string>
#include <list>
#include <vector>
+#include <fstream>
+#include <algorithm>
#include "dcpparser.h"
@@ -76,6 +85,43 @@ static int ReadEndNode( xml_reader_t *p_xmlReader, string p_node,
return -1;
}
+/* 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;
+}
+
+static bool isBlank( char c )
+{
+ return ( c == '\n' || c == '\r' || c == '\t' || c == ' ' );
+};
+
typedef enum {
CHUNK_UNKNOWN = 0,
CHUNK_PATH,
@@ -230,11 +276,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;
@@ -337,6 +385,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 +427,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 +450,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());
@@ -629,6 +702,19 @@ 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 )
+{
+ 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;
@@ -1142,6 +1228,7 @@ int Reel::ParseAsset(string p_node, int p_type, TrackType_t e_track) {
} else if (node == "KeyId") {
if ( 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))
return -1;
@@ -1409,3 +1496,604 @@ int CPL::DummyParse(string p_node, int p_type)
return -1;
}
+
+/*
+ * 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 == 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 = 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 = 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 = ReadNextNode( p_xml_reader, s_node ) ) > 0 )
+ {
+ switch( i_type )
+ {
+ case XML_READER_STARTELEM:
+ if( s_node == "enc:CipherValue" )
+ {
+ if( 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;
+
+ /* get RSA private key file path */
+ if( rsa_key.setPath() )
+ goto error;
+
+ /* read private key from file */
+ if( rsa_key.readPEM() )
+ goto error;
+
+ /* 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(), isBlank ),
+ s_cipher_text_b64.end() );
+ }
+ catch( ... )
+ {
+ msg_Err( this->p_demux, "error while handling string" );
+ goto error;
+ }
+
+ /* 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 error;
+ }
+
+ /* 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 error;
+ }
+ 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 error;
+ }
+
+ /* 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 error;
+ }
+
+ /* 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 error;
+ }
+ /* 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 error;
+ }
+
+ 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 error;
+ }
+
+ /* interpret the plaintext data */
+ switch( length )
+ {
+ case 138: /* SMPTE DCP */
+ if( this->extractInfo( ps_plain_text, true ) )
+ goto error;
+ break;
+ case 136: /* Interop DCP */
+ if( this->extractInfo( ps_plain_text, false ) )
+ goto error;
+ break;
+ case -1:
+ msg_Err( this->p_demux, "could not decrypt" );
+ goto error;
+ default:
+ msg_Err( this->p_demux, "CipherValue field length does not match SMPTE nor Interop standards" );
+ goto error;
+ }
+
+ 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 VLC_SUCCESS;
+
+error:
+ 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 VLC_EGENERIC;
+}
+
+/* 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;
+}
diff --git a/modules/access/dcp/dcpparser.h b/modules/access/dcp/dcpparser.h
index 32059f5..6d1f944 100644
--- a/modules/access/dcp/dcpparser.h
+++ b/modules/access/dcp/dcpparser.h
@@ -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
@@ -27,6 +28,10 @@
/**
* @file dcpparser.h
* @brief Parse DCP XML files
+ *
+ * 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
*/
@@ -42,6 +47,13 @@
#include <vlc_demux.h>
#include <vlc_plugin.h>
+/* ASDCP header */
+#include <AS_DCP.h>
+
+/* gcrypt headers */
+#include <gcrypt.h>
+#include <vlc_gcrypt.h>
+
#include <iostream>
#include <string>
#include <list>
@@ -67,6 +79,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() */
@@ -77,6 +91,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 +101,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 +116,10 @@ struct dcp_t
delete(p_asset_list);
}
+ if ( p_key_list != NULL ) {
+ vlc_delete_all(*p_key_list);
+ delete(p_key_list);
+ }
}
};
@@ -164,6 +184,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 +193,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 +203,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 +339,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) {}
+ ~KDM() {};
+
+ 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 ) { }
+
+ 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 ) { }
+ ~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 */
--
1.7.9.5
More information about the vlc-devel
mailing list