You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

673 lines
19 KiB

/*****************************************************************************
* 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_configuration.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_demux, 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_demux, 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_demux, this->p_xmlReader, s_node ) ) > 0 )
{
switch( i_type )
{
case XML_READER_STARTELEM:
if( s_node != "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 != "EncryptedKey" )
goto error;
while( ( i_type = XmlFile::ReadNextNode( this->p_demux, p_xml_reader, s_node ) ) > 0 )
{
switch( i_type )
{
case XML_READER_STARTELEM:
if( s_node == "CipherValue" )
{
if( XmlFile::ReadEndNode( this->p_demux, 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 static_cast<size_t>( -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] = { NULL };
gcry_error_t err;
/* 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( int 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( int 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( int 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;
}