]> git.sesse.net Git - vlc/blob - modules/access/dcp/dcpdecrypt.cpp
4590c6288c3622241031763bcf41f154f669f02a
[vlc] / modules / access / dcp / dcpdecrypt.cpp
1 /*****************************************************************************
2  * Copyright (C) 2013 VLC authors and VideoLAN
3  *
4  * Author:
5  *          Simona-Marinela Prodea <simona dot marinela dot prodea at gmail dot com>
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2.1 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
20  *****************************************************************************/
21
22 /**
23  * The code used for reading a DER-encoded private key, that is,
24  * RSAKey::parseTag function and RSAKey::readDER function,
25  * is taken almost as is from libgcrypt tests/fipsdrv.c
26  */
27
28 /**
29  * @file dcpdecrypt.cpp
30  * @brief Handle encrypted DCPs
31  */
32
33 #ifdef HAVE_CONFIG_H
34 # include "config.h"
35 #endif
36
37 /* VLC core API headers */
38 #include <vlc_common.h>
39 #include <vlc_xml.h>
40 #include <vlc_strings.h>
41
42 #include <fstream>
43 #include <algorithm>
44 #include <cctype>
45
46 #include "dcpparser.h"
47
48 /* creates a printable, RFC 4122-conform UUID, from a given array of bytes
49  */
50 static string createUUID( unsigned char *ps_string )
51 {
52     string s_uuid;
53     char h[3];
54     int i, ret;
55
56     if( ! ps_string )
57         return "";
58
59     try
60     {
61         s_uuid.append( "urn:uuid:" );
62         for( i = 0; i < 16; i++ )
63         {
64             ret = snprintf( h, 3, "%02hhx", ps_string[i] );  /* each byte can be written as 2 hex digits */
65             if( ret != 2 )
66                 return "";
67             s_uuid.append( h );
68             if( i == 3 || i == 5 || i == 7 || i == 9 )
69                 s_uuid.append( "-" );
70         }
71     }
72     catch( ... )
73     {
74         return "";
75     }
76
77     return s_uuid;
78 }
79
80 /*
81  * KDM class
82  */
83
84 int KDM::Parse()
85 {
86     string s_node, s_value;
87     const string s_root_node = "DCinemaSecurityMessage";
88     int type;
89
90     AESKeyList *_p_key_list = NULL;
91
92     /* init XML parser */
93     if( this->OpenXml() )
94     {
95         msg_Err( p_demux, "failed to initialize KDM XML parser" );
96         return VLC_EGENERIC;
97     }
98
99     msg_Dbg( this->p_demux, "parsing KDM..." );
100
101     /* read first node and check if it is a KDM */
102     if( ! ( ( XML_READER_STARTELEM == XmlFile::ReadNextNode( this->p_xmlReader, s_node ) ) && ( s_node == s_root_node ) ) )
103     {
104         msg_Err( this->p_demux, "not a valid XML KDM" );
105         goto error;
106     }
107
108     while( ( type = XmlFile::ReadNextNode( this->p_xmlReader, s_node ) ) > 0 )
109         if( type == XML_READER_STARTELEM && s_node == "AuthenticatedPrivate" )
110         {
111             _p_key_list = new (nothrow) AESKeyList;
112             if( unlikely( _p_key_list == NULL ) )
113                 goto error;
114             p_dcp->p_key_list = _p_key_list;
115             if( this->ParsePrivate( s_node, type ) )
116                 goto error;
117
118             /* keys found, so break */
119             break;
120         }
121
122     if ( (_p_key_list == NULL) ||  (_p_key_list->size() == 0) )
123     {
124         msg_Err( p_demux, "Key list empty" );
125         goto error;
126     }
127
128     /* close KDM XML */
129     this->CloseXml();
130     return VLC_SUCCESS;
131 error:
132     this->CloseXml();
133     return VLC_EGENERIC;
134 }
135
136 int KDM::ParsePrivate( const string _s_node, int _i_type )
137 {
138     string s_node;
139     int i_type;
140     AESKey *p_key;
141
142     /* check that we are where we're supposed to be */
143     if( _i_type != XML_READER_STARTELEM )
144         goto error;
145     if( _s_node != "AuthenticatedPrivate" )
146         goto error;
147
148     /* loop on EncryptedKey nodes */
149     while( ( i_type = XmlFile::ReadNextNode( this->p_xmlReader, s_node ) ) > 0 )
150     {
151         switch( i_type )
152         {
153             case XML_READER_STARTELEM:
154                 if( s_node != "enc:EncryptedKey" )
155                     goto error;
156                 p_key = new (nothrow) AESKey( this->p_demux );
157                 if( unlikely( p_key == NULL ) )
158                     return VLC_EGENERIC;
159                 if( p_key->Parse( p_xmlReader, s_node, i_type ) )
160                 {
161                     delete p_key;
162                     return VLC_EGENERIC;
163                 }
164                 p_dcp->p_key_list->push_back( p_key );
165                 break;
166
167             case XML_READER_ENDELEM:
168                 if( s_node == _s_node )
169                     return VLC_SUCCESS;
170                 break;
171             default:
172             case XML_READER_TEXT:
173                 goto error;
174         }
175     }
176
177     /* shouldn't get here */
178 error:
179     msg_Err( p_demux, "error while parsing AuthenticatedPrivate portion of KDM" );
180     return VLC_EGENERIC;
181 }
182
183 /*
184  * AESKey class
185  */
186
187 int AESKey::Parse( xml_reader_t *p_xml_reader, string _s_node, int _i_type)
188 {
189     string s_node;
190     string s_value;
191     int i_type;
192
193     if( _i_type != XML_READER_STARTELEM)
194         goto error;
195     if( _s_node != "enc:EncryptedKey" )
196         goto error;
197
198     while( ( i_type = XmlFile::ReadNextNode( p_xml_reader, s_node ) ) > 0 )
199     {
200         switch( i_type )
201         {
202             case XML_READER_STARTELEM:
203                 if( s_node == "enc:CipherValue" )
204                 {
205                     if( XmlFile::ReadEndNode( p_xml_reader, s_node, i_type, s_value ) )
206                         goto error;
207                     if( this->decryptRSA( s_value ) )
208                         return VLC_EGENERIC;
209                 }
210                 break;
211             case XML_READER_ENDELEM:
212                 if( s_node == _s_node )
213                     return VLC_SUCCESS;
214                 break;
215             default:
216             case XML_READER_TEXT:
217                 goto error;
218         }
219     }
220
221     /* shouldn't get here */
222 error:
223     msg_Err( this->p_demux, "error while parsing EncryptedKey" );
224     return VLC_EGENERIC;
225 }
226
227 /* decrypts the RSA encrypted text read from the XML file,
228  * and saves the AES key and the other needed info
229  * uses libgcrypt for decryption
230  */
231 int AESKey::decryptRSA( string s_cipher_text_b64 )
232 {
233     RSAKey rsa_key( this->p_demux );
234     unsigned char *ps_cipher_text = NULL;
235     unsigned char *ps_plain_text = NULL;
236     gcry_mpi_t cipher_text_mpi = NULL;
237     gcry_sexp_t cipher_text_sexp = NULL;
238     gcry_sexp_t plain_text_sexp = NULL;
239     gcry_mpi_t plain_text_mpi = NULL;
240     gcry_sexp_t tmp_sexp = NULL;
241     gcry_error_t err;
242     size_t length;
243     int i_ret = VLC_EGENERIC;
244
245     /* get RSA private key file path */
246     if( rsa_key.setPath() )
247         goto end;
248
249     /* read private key from file */
250     if( rsa_key.readPEM() )
251         goto end;
252
253     /* remove spaces and newlines from encoded cipher text
254      * (usually added for indentation in XML files)
255      * */
256     try
257     {
258         s_cipher_text_b64.erase( remove_if( s_cipher_text_b64.begin(), s_cipher_text_b64.end(), static_cast<int(*)(int)>(isspace) ),
259                                  s_cipher_text_b64.end() );
260     }
261     catch( ... )
262     {
263         msg_Err( this->p_demux, "error while handling string" );
264         goto end;
265     }
266
267     /* decode cipher from BASE64 to binary */
268     if( ! ( length = vlc_b64_decode_binary( &ps_cipher_text, s_cipher_text_b64.c_str() ) ) )
269     {
270         msg_Err( this->p_demux, "could not decode cipher from Base64" );
271         goto end;
272     }
273
274     /* initialize libgcrypt */
275     vlc_gcrypt_init ();
276
277     /* create S-expression for ciphertext */
278     if( ( err = gcry_mpi_scan( &cipher_text_mpi, GCRYMPI_FMT_USG, ps_cipher_text, 256, NULL ) ) )
279     {
280         msg_Err( this->p_demux, "could not scan MPI from cipher text: %s", gcry_strerror( err ) );
281         goto end;
282     }
283     if( ( err = gcry_sexp_build( &cipher_text_sexp, NULL, "(enc-val(flags oaep)(rsa(a %m)))", cipher_text_mpi ) ) )
284     {
285         msg_Err( this->p_demux, "could not build S-expression for cipher text: %s", gcry_strerror( err ) );
286         goto end;
287     }
288
289     /* decrypt */
290     if( ( err = gcry_pk_decrypt( &plain_text_sexp, cipher_text_sexp, rsa_key.priv_key ) ) )
291     {
292         msg_Err( this->p_demux, "error while decrypting RSA encrypted info: %s", gcry_strerror( err ) );
293         goto end;
294     }
295
296     /* extract plain-text from S-expression */
297     if( ! ( tmp_sexp = gcry_sexp_find_token( plain_text_sexp, "value", 0 ) ) )
298         /* when using padding flags, the decrypted S-expression is of the form
299          * "(value <plaintext>)", where <plaintext> is an MPI */
300     {
301         msg_Err( this->p_demux, "decrypted text is in an unexpected form; decryption may have failed" );
302         goto end;
303     }
304     /* we could have used the gcry_sexp_nth_data to get the data directly,
305      * but as that function is newly introduced (libgcrypt v1.6),
306      * we prefer compatibility, even though that means passing the data through an MPI first */
307     if( ! ( plain_text_mpi = gcry_sexp_nth_mpi( tmp_sexp, 1, GCRYMPI_FMT_USG ) ) )
308     {
309         msg_Err( this->p_demux, "could not extract MPI from decrypted S-expression" );
310         goto end;
311     }
312
313     if( ( err = gcry_mpi_aprint( GCRYMPI_FMT_USG, &ps_plain_text, &length, plain_text_mpi ) ) )
314     {
315         msg_Err( this->p_demux, "error while extracting plain text from MPI: %s", gcry_strerror( err ) );
316         goto end;
317     }
318
319     /* interpret the plaintext data */
320     switch( length )
321     {
322         case 138:   /* SMPTE    DCP */
323             if( this->extractInfo( ps_plain_text, true ) )
324                 goto end;
325             break;
326         case 134:   /* Interop  DCP */
327             if( this->extractInfo( ps_plain_text, false ) )
328                 goto end;
329             break;
330         case -1:
331             msg_Err( this->p_demux, "could not decrypt" );
332             goto end;
333         default:
334             msg_Err( this->p_demux, "CipherValue field length does not match SMPTE nor Interop standards" );
335             goto end;
336     }
337
338     i_ret = VLC_SUCCESS;
339
340 end:
341     free( ps_cipher_text );
342     gcry_mpi_release( cipher_text_mpi );
343     gcry_sexp_release( cipher_text_sexp );
344     gcry_sexp_release( plain_text_sexp );
345     gcry_mpi_release( plain_text_mpi );
346     gcry_sexp_release( tmp_sexp );
347     gcry_free( ps_plain_text );
348     return i_ret;
349 }
350
351 /* extracts and saves the AES key info from the plaintext;
352  * parameter smpte is true for SMPTE DCP, false for Interop;
353  * see SMPTE 430-1-2006, section 6.1.2 for the exact structure of the plaintext
354  */
355 int AESKey::extractInfo( unsigned char * ps_plain_text, bool smpte )
356 {
357
358     string s_rsa_structID( "f1dc124460169a0e85bc300642f866ab" ); /* unique Structure ID for all RSA-encrypted AES keys in a KDM */
359     string s_carrier;
360     char psz_hex[3];
361     int i_ret, i_pos = 0;
362
363     /* check for the structure ID */
364     while( i_pos < 16 )
365     {
366         i_ret = snprintf( psz_hex, 3, "%02hhx", ps_plain_text[i_pos] );
367         if( i_ret != 2 )
368         {
369             msg_Err( this->p_demux, "error while extracting structure ID from decrypted cipher" );
370             return VLC_EGENERIC;
371         }
372         try
373         {
374             s_carrier.append( psz_hex );
375         }
376         catch( ... )
377         {
378             msg_Err( this->p_demux, "error while handling string" );
379             return VLC_EGENERIC;
380         }
381         i_pos++;
382     }
383     if( s_carrier.compare( s_rsa_structID ) )
384     {
385         msg_Err( this->p_demux, "incorrect RSA structure ID: KDM may be broken" );
386         return VLC_EGENERIC;
387     }
388
389     i_pos += 36;        /* TODO thumbprint, CPL ID */
390     if( smpte )         /* only SMPTE DCPs have the 4-byte "KeyType" field */
391         i_pos += 4;
392
393     /* extract the AES key UUID */
394     if( ( this->s_key_id = createUUID( ps_plain_text + i_pos ) ).empty() )
395     {
396         msg_Err( this->p_demux, "error while extracting AES Key UUID" );
397         return VLC_EGENERIC;
398     }
399     i_pos += 16;
400
401     i_pos += 50; /* TODO KeyEpoch */
402
403     /* extract the AES key */
404     memcpy( this->ps_key, ps_plain_text + i_pos, 16 );
405
406     return VLC_SUCCESS;
407 }
408
409
410 /*
411  * RSAKey class
412  */
413
414 /*
415  * gets the private key path (always stored in the VLC config dir and called "priv.key" )
416  */
417 int RSAKey::setPath( )
418 {
419     char *psz_config_dir = NULL;
420
421     if( ! ( psz_config_dir = config_GetUserDir( VLC_CONFIG_DIR ) ) )
422     {
423         msg_Err( this->p_demux, "could not read user config dir" );
424         goto error;
425     }
426     try
427     {
428         this->s_path.assign( psz_config_dir );
429         this->s_path.append( "/priv.key" );
430     }
431     catch( ... )
432     {
433         msg_Err( this->p_demux, "error while handling string" );
434         goto error;
435     }
436
437     free( psz_config_dir );
438     return VLC_SUCCESS;
439
440 error:
441     free( psz_config_dir );
442     return VLC_EGENERIC;
443 }
444
445 /*
446  * reads the RSA private key from file
447  * the file must be conform to PCKS#1, PEM-encoded, unencrypted
448  */
449 int RSAKey::readPEM( )
450 {
451     string s_header_tag( "-----BEGIN RSA PRIVATE KEY-----" );
452     string s_footer_tag( "-----END RSA PRIVATE KEY-----" );
453     string s_line;
454     string s_data_b64;
455     unsigned char *ps_data_der = NULL;
456     size_t length;
457
458     /* open key file */
459     ifstream file( this->s_path.c_str(), ios::in );
460     if( ! file.is_open() )
461     {
462         msg_Err( this->p_demux, "could not open private key file" );
463         goto error;
464     }
465
466     /* check for header tag */
467     if( ! getline( file, s_line ) )
468     {
469         msg_Err( this->p_demux, "could not read private key file" );
470         goto error;
471     }
472     if( s_line.compare( s_header_tag ) )
473     {
474         msg_Err( this->p_demux, "unexpected header tag found in private key file" );
475         goto error;
476     }
477
478     /* read file until footer tag is found */
479     while( getline( file, s_line ) )
480     {
481         if( ! s_line.compare( s_footer_tag ) )
482             break;
483         try
484         {
485             s_data_b64.append( s_line );
486         }
487         catch( ... )
488         {
489             msg_Err( this->p_demux, "error while handling string" );
490             goto error;
491         }
492     }
493     if( ! file )
494     {
495         msg_Err( this->p_demux, "error while reading private key file; footer tag may be missing" );
496         goto error;
497     }
498
499     /* decode data from Base64 */
500     if( ! ( length = vlc_b64_decode_binary( &ps_data_der, s_data_b64.c_str() ) ) )
501     {
502         msg_Err( this->p_demux, "could not decode from Base64" );
503         goto error;
504     }
505
506     /* extract key S-expression from DER-encoded data */
507     if( this->readDER( ps_data_der, length ) )
508         goto error;
509
510     /* clear data */
511     free( ps_data_der );
512     return VLC_SUCCESS;
513
514 error:
515     free( ps_data_der );
516     return VLC_EGENERIC;
517 }
518
519 /*
520  * Parse the DER-encoded data at ps_data_der
521  * saving the key in an S-expression
522  */
523 int RSAKey::readDER( unsigned char const* ps_data_der, size_t length )
524 {
525     struct tag_info tag_inf;
526     gcry_mpi_t key_params[8];
527     gcry_error_t err;
528     int i;
529
530     /* parse the ASN1 structure */
531     if( parseTag( &ps_data_der, &length, &tag_inf )
532             || tag_inf.tag != TAG_SEQUENCE || tag_inf.class_ || !tag_inf.cons || tag_inf.ndef )
533         goto bad_asn1;
534     if( parseTag( &ps_data_der, &length, &tag_inf )
535        || tag_inf.tag != TAG_INTEGER || tag_inf.class_ || tag_inf.cons || tag_inf.ndef )
536         goto bad_asn1;
537     if( tag_inf.length != 1 || *ps_data_der )
538         goto bad_asn1;  /* The value of the first integer is no 0. */
539     ps_data_der += tag_inf.length;
540     length -= tag_inf.length;
541
542     for( i = 0; i < 8; i++ )
543     {
544         if( parseTag( &ps_data_der, &length, &tag_inf )
545                 || tag_inf.tag != TAG_INTEGER || tag_inf.class_ || tag_inf.cons || tag_inf.ndef )
546             goto bad_asn1;
547         err = gcry_mpi_scan( key_params + i, GCRYMPI_FMT_USG, ps_data_der, tag_inf.length, NULL );
548         if( err )
549         {
550             msg_Err( this->p_demux, "error scanning RSA parameter %d: %s", i, gpg_strerror( err ) );
551             goto error;
552         }
553         ps_data_der += tag_inf.length;
554         length -= tag_inf.length;
555     }
556
557     /* Convert from OpenSSL parameter ordering to the OpenPGP order.
558      * First check that p < q; if not swap p and q and recompute u.
559      */
560     if( gcry_mpi_cmp( key_params[3], key_params[4] ) > 0 )
561     {
562         gcry_mpi_swap( key_params[3], key_params[4] );
563         gcry_mpi_invm( key_params[7], key_params[3], key_params[4] );
564     }
565
566     /* Build the S-expression.  */
567     err = gcry_sexp_build( & this->priv_key, NULL,
568                          "(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
569                          key_params[0], key_params[1], key_params[2],
570                          key_params[3], key_params[4], key_params[7] );
571     if( err )
572     {
573         msg_Err( this->p_demux, "error building S-expression: %s", gpg_strerror( err ) );
574         goto error;
575     }
576
577     /* clear data */
578     for( i = 0; i < 8; i++ )
579         gcry_mpi_release( key_params[i] );
580     return VLC_SUCCESS;
581
582 bad_asn1:
583     msg_Err( this->p_demux, "could not parse ASN1 structure; key might be corrupted" );
584
585 error:
586     for( i = 0; i < 8; i++ )
587         gcry_mpi_release( key_params[i] );
588     return VLC_EGENERIC;
589 }
590
591 /*
592  * Parse the buffer at the address BUFFER which consists of the number
593  * of octets as stored at BUFLEN.  Return the tag and the length part
594  * from the TLV triplet.  Update BUFFER and BUFLEN on success.  Checks
595  * that the encoded length does not exhaust the length of the provided
596  * buffer.
597  */
598 int RSAKey::parseTag( unsigned char const **buffer, size_t *buflen, struct tag_info *ti)
599 {
600   int c;
601   unsigned long tag;
602   const unsigned char *buf = *buffer;
603   size_t length = *buflen;
604
605   ti->length = 0;
606   ti->ndef = 0;
607   ti->nhdr = 0;
608
609   /* Get the tag */
610   if (!length)
611     return -1; /* Premature EOF.  */
612   c = *buf++; length--;
613   ti->nhdr++;
614
615   ti->class_ = (c & 0xc0) >> 6;
616   ti->cons   = !!(c & 0x20);
617   tag        = (c & 0x1f);
618
619   if (tag == 0x1f)
620     {
621       tag = 0;
622       do
623         {
624           tag <<= 7;
625           if (!length)
626             return -1; /* Premature EOF.  */
627           c = *buf++; length--;
628           ti->nhdr++;
629           tag |= (c & 0x7f);
630         }
631       while ( (c & 0x80) );
632     }
633   ti->tag = tag;
634
635   /* Get the length */
636   if (!length)
637     return -1; /* Premature EOF. */
638   c = *buf++; length--;
639   ti->nhdr++;
640
641   if ( !(c & 0x80) )
642     ti->length = c;
643   else if (c == 0x80)
644     ti->ndef = 1;
645   else if (c == 0xff)
646     return -1; /* Forbidden length value.  */
647   else
648     {
649       unsigned long len = 0;
650       int count = c & 0x7f;
651
652       for (; count; count--)
653         {
654           len <<= 8;
655           if (!length)
656             return -1; /* Premature EOF.  */
657           c = *buf++; length--;
658           ti->nhdr++;
659           len |= (c & 0xff);
660         }
661       ti->length = len;
662     }
663
664   if (ti->class_ == 0 && !ti->tag)
665     ti->length = 0;
666
667   if (ti->length > length)
668     return -1; /* Data larger than buffer.  */
669
670   *buffer = buf;
671   *buflen = length;
672   return 0;
673 }