From 257fce15e8b4dfa112d039e7888e3ec04e523198 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Mon, 13 May 2019 16:08:33 +0100 Subject: [PATCH] swaroop: basics of encrypted MP4 playback. --- src/lib/decrypted_ecinema_kdm.cc | 38 ++++++++++++++++++++++++++--- src/lib/decrypted_ecinema_kdm.h | 12 +++++++++- src/lib/encrypted_ecinema_kdm.cc | 18 ++++++++++++-- src/lib/encrypted_ecinema_kdm.h | 12 +++++++++- src/lib/ffmpeg.cc | 9 ++++--- src/lib/ffmpeg_content.cc | 20 ++++++++++++---- src/lib/ffmpeg_content.h | 26 ++++++++++++++++---- src/lib/ffmpeg_decoder.cc | 7 +++++- src/lib/ffmpeg_examiner.cc | 7 ++++++ src/lib/ffmpeg_examiner.h | 10 ++++++++ src/lib/util.h | 4 ++++ src/tools/dcpomatic_ecinema.cc | 12 +++++++++- src/tools/dcpomatic_player.cc | 20 ++++++++++++++-- src/wx/swaroop_controls.cc | 41 +++++++++++++++++++++++++++++++- src/wx/swaroop_controls.h | 2 ++ 15 files changed, 214 insertions(+), 24 deletions(-) diff --git a/src/lib/decrypted_ecinema_kdm.cc b/src/lib/decrypted_ecinema_kdm.cc index 2cb4f61d7..a03004e43 100644 --- a/src/lib/decrypted_ecinema_kdm.cc +++ b/src/lib/decrypted_ecinema_kdm.cc @@ -22,21 +22,53 @@ #include "encrypted_ecinema_kdm.h" #include "decrypted_ecinema_kdm.h" +#include "exceptions.h" #include +#include #include +#include +#include +#include +using std::string; +using std::runtime_error; using dcp::Certificate; -DecryptedECinemaKDM::DecryptedECinemaKDM (dcp::Key content_key) - : _content_key (content_key) +DecryptedECinemaKDM::DecryptedECinemaKDM (string id, dcp::Key content_key) + : _id (id) + , _content_key (content_key) { } +DecryptedECinemaKDM::DecryptedECinemaKDM (EncryptedECinemaKDM kdm, string private_key) + : _id (kdm.id()) +{ + /* Read the private key */ + + BIO* bio = BIO_new_mem_buf (const_cast (private_key.c_str()), -1); + if (!bio) { + throw runtime_error ("could not create memory BIO"); + } + + RSA* rsa = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0); + if (!rsa) { + throw FileError ("could not read RSA private key file", private_key); + } + + uint8_t value[RSA_size(rsa)]; + int const len = RSA_private_decrypt (kdm.key().size(), kdm.key().data().get(), value, rsa, RSA_PKCS1_OAEP_PADDING); + if (len == -1) { + throw KDMError (ERR_error_string(ERR_get_error(), 0), ""); + } + + _content_key = dcp::Key (value, len); +} + EncryptedECinemaKDM DecryptedECinemaKDM::encrypt (Certificate recipient) { - return EncryptedECinemaKDM (_content_key, recipient); + return EncryptedECinemaKDM (_id, _content_key, recipient); } #endif diff --git a/src/lib/decrypted_ecinema_kdm.h b/src/lib/decrypted_ecinema_kdm.h index b0fc2064d..f61402b7b 100644 --- a/src/lib/decrypted_ecinema_kdm.h +++ b/src/lib/decrypted_ecinema_kdm.h @@ -28,11 +28,21 @@ class DecryptedECinemaKDM { public: - DecryptedECinemaKDM (dcp::Key content_key); + DecryptedECinemaKDM (std::string id, dcp::Key content_key); + DecryptedECinemaKDM (EncryptedECinemaKDM kdm, std::string private_key); EncryptedECinemaKDM encrypt (dcp::Certificate recipient); + std::string id () const { + return _id; + } + + dcp::Key key () const { + return _content_key; + } + private: + std::string _id; /** unenecrypted content key */ dcp::Key _content_key; }; diff --git a/src/lib/encrypted_ecinema_kdm.cc b/src/lib/encrypted_ecinema_kdm.cc index e277eb997..ab9e15e85 100644 --- a/src/lib/encrypted_ecinema_kdm.cc +++ b/src/lib/encrypted_ecinema_kdm.cc @@ -23,6 +23,8 @@ #include "encrypted_ecinema_kdm.h" #include #include +#include +#include #include #include #include @@ -32,13 +34,24 @@ using std::string; using boost::shared_ptr; using dcp::Certificate; -EncryptedECinemaKDM::EncryptedECinemaKDM (dcp::Key content_key, Certificate recipient) +EncryptedECinemaKDM::EncryptedECinemaKDM (string id, dcp::Key content_key, Certificate recipient) + : _id (id) { RSA* rsa = recipient.public_key (); _content_key = dcp::Data (RSA_size(rsa)); int const N = RSA_public_encrypt (content_key.length(), content_key.value(), _content_key.data().get(), rsa, RSA_PKCS1_OAEP_PADDING); } +EncryptedECinemaKDM::EncryptedECinemaKDM (string xml) +{ + cxml::Document doc ("ECinemaSecurityMessage"); + doc.read_string (xml); + _id = doc.string_child ("Id"); + _content_key = dcp::Data (256); + int const len = dcp::base64_decode (doc.string_child("Key"), _content_key.data().get(), _content_key.size()); + _content_key.set_size (len); +} + string EncryptedECinemaKDM::as_xml () const { @@ -57,7 +70,8 @@ EncryptedECinemaKDM::as_xml () const } xmlpp::Document document; - xmlpp::Element* root = document.create_root_node ("ECinemaSecurityMesage"); + xmlpp::Element* root = document.create_root_node ("ECinemaSecurityMessage"); + root->add_child("Id")->add_child_text(_id); root->add_child("Key")->add_child_text(lines); return document.write_to_string ("UTF-8"); } diff --git a/src/lib/encrypted_ecinema_kdm.h b/src/lib/encrypted_ecinema_kdm.h index fc6fbdb65..ece1e3161 100644 --- a/src/lib/encrypted_ecinema_kdm.h +++ b/src/lib/encrypted_ecinema_kdm.h @@ -32,14 +32,24 @@ class DecryptedECinemaKDM; class EncryptedECinemaKDM { public: + explicit EncryptedECinemaKDM (std::string xml); std::string as_xml () const; + std::string id () const { + return _id; + } + + dcp::Data key () const { + return _content_key; + } + private: friend class DecryptedECinemaKDM; - EncryptedECinemaKDM (dcp::Key key, dcp::Certificate recipient); + EncryptedECinemaKDM (std::string id, dcp::Key key, dcp::Certificate recipient); + std::string _id; /** encrypted content key */ dcp::Data _content_key; }; diff --git a/src/lib/ffmpeg.cc b/src/lib/ffmpeg.cc index ab5148cfa..d6a592f88 100644 --- a/src/lib/ffmpeg.cc +++ b/src/lib/ffmpeg.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2016 Carl Hetherington + Copyright (C) 2013-2019 Carl Hetherington This file is part of DCP-o-matic. @@ -27,8 +27,10 @@ #include "dcpomatic_log.h" #include "ffmpeg_subtitle_stream.h" #include "ffmpeg_audio_stream.h" +#include "decrypted_ecinema_kdm.h" #include "digester.h" #include "compose.hpp" +#include "config.h" #include extern "C" { #include @@ -123,8 +125,9 @@ FFmpeg::setup_general () */ av_dict_set (&options, "analyzeduration", raw_convert (5 * 60 * 1000000).c_str(), 0); av_dict_set (&options, "probesize", raw_convert (5 * 60 * 1000000).c_str(), 0); - if (_ffmpeg_content->decryption_key()) { - av_dict_set (&options, "decryption_key", _ffmpeg_content->decryption_key()->c_str(), 0); + if (_ffmpeg_content->kdm()) { + DecryptedECinemaKDM kdm (_ffmpeg_content->kdm().get(), Config::instance()->decryption_chain()->key().get()); + av_dict_set (&options, "decryption_key", kdm.key().hex().c_str(), 0); } int e = avformat_open_input (&_format_context, 0, 0, &options); diff --git a/src/lib/ffmpeg_content.cc b/src/lib/ffmpeg_content.cc index 69d743215..e291b8e7c 100644 --- a/src/lib/ffmpeg_content.cc +++ b/src/lib/ffmpeg_content.cc @@ -61,6 +61,7 @@ using namespace dcpomatic; int const FFmpegContentProperty::SUBTITLE_STREAMS = 100; int const FFmpegContentProperty::SUBTITLE_STREAM = 101; int const FFmpegContentProperty::FILTERS = 102; +int const FFmpegContentProperty::KDM = 103; FFmpegContent::FFmpegContent (boost::filesystem::path p) : Content (p) @@ -125,7 +126,6 @@ FFmpegContent::FFmpegContent (cxml::ConstNodePtr node, int version, list _color_trc = get_optional_enum(node, "ColorTransferCharacteristic"); _colorspace = get_optional_enum(node, "Colorspace"); _bits_per_pixel = node->optional_number_child ("BitsPerPixel"); - _decryption_key = node->optional_string_child ("DecryptionKey"); _encrypted = node->optional_bool_child("Encrypted").get_value_or(false); } @@ -248,9 +248,6 @@ FFmpegContent::as_xml (xmlpp::Node* node, bool with_paths) const if (_bits_per_pixel) { node->add_child("BitsPerPixel")->add_child_text (raw_convert (*_bits_per_pixel)); } - if (_decryption_key) { - node->add_child("DecryptionKey")->add_child_text (_decryption_key.get()); - } if (_encrypted) { node->add_child("Encypted")->add_child_text ("1"); } @@ -325,6 +322,10 @@ FFmpegContent::examine (shared_ptr film, shared_ptr job) if (examiner->has_video ()) { set_default_colour_conversion (); } + +#ifdef DCPOMATIC_VARIANT_SWAROOP + _id = examiner->id (); +#endif } string @@ -687,3 +688,14 @@ FFmpegContent::take_settings_from (shared_ptr c) Content::take_settings_from (c); _filters = fc->_filters; } + +#ifdef DCPOMATIC_VARIANT_SWAROOP +void +FFmpegContent::add_kdm (EncryptedECinemaKDM kdm) +{ + ChangeSignaller cc (this, FFmpegContentProperty::KDM); + boost::mutex::scoped_lock lm (_mutex); + _kdm = kdm; + +} +#endif diff --git a/src/lib/ffmpeg_content.h b/src/lib/ffmpeg_content.h index 8871301b1..6c572f242 100644 --- a/src/lib/ffmpeg_content.h +++ b/src/lib/ffmpeg_content.h @@ -21,6 +21,9 @@ #ifndef DCPOMATIC_FFMPEG_CONTENT_H #define DCPOMATIC_FFMPEG_CONTENT_H +#ifdef DCPOMATIC_VARIANT_SWAROOP +#include "encrypted_ecinema_kdm.h" +#endif #include "content.h" #include "audio_stream.h" @@ -41,6 +44,7 @@ public: /** The chosen subtitle stream, or something about it */ static int const SUBTITLE_STREAM; static int const FILTERS; + static int const KDM; }; class FFmpegContent : public Content @@ -98,16 +102,25 @@ public: void signal_subtitle_stream_changed (); - boost::optional decryption_key () const { - boost::mutex::scoped_lock lm (_mutex); - return _decryption_key; - } +#ifdef DCPOMATIC_VARIANT_SWAROOP bool encrypted () const { boost::mutex::scoped_lock lm (_mutex); return _encrypted; } + void add_kdm (EncryptedECinemaKDM kdm); + + boost::optional kdm () const { + return _kdm; + } + + boost::optional id () const { + return _id; + } + +#endif + private: void add_properties (boost::shared_ptr film, std::list &) const; @@ -125,8 +138,11 @@ private: boost::optional _color_trc; boost::optional _colorspace; boost::optional _bits_per_pixel; - boost::optional _decryption_key; +#ifdef DCPOMATIC_VARIANT_SWAROOP bool _encrypted; + boost::optional _kdm; + boost::optional _id; +#endif }; #endif diff --git a/src/lib/ffmpeg_decoder.cc b/src/lib/ffmpeg_decoder.cc index 0e65a6d6b..c52723da3 100644 --- a/src/lib/ffmpeg_decoder.cc +++ b/src/lib/ffmpeg_decoder.cc @@ -59,7 +59,6 @@ extern "C" { #include "i18n.h" - using std::cout; using std::string; using std::vector; @@ -159,6 +158,12 @@ FFmpegDecoder::flush () bool FFmpegDecoder::pass () { +#ifdef DCPOMATIC_VARIANT_SWAROOP + if (_ffmpeg_content->encrypted() && !_ffmpeg_content->kdm()) { + return true; + } +#endif + int r = av_read_frame (_format_context, &_packet); /* AVERROR_INVALIDDATA can apparently be returned sometimes even when av_read_frame diff --git a/src/lib/ffmpeg_examiner.cc b/src/lib/ffmpeg_examiner.cc index 382b71b83..a4c5eb128 100644 --- a/src/lib/ffmpeg_examiner.cc +++ b/src/lib/ffmpeg_examiner.cc @@ -166,6 +166,13 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr c, shared_ptrmetadata, SWAROOP_ID_TAG, 0, 0); + if (e) { + _id = e->value; + } +#endif } void diff --git a/src/lib/ffmpeg_examiner.h b/src/lib/ffmpeg_examiner.h index d2e6e1a0a..1c0dad3dc 100644 --- a/src/lib/ffmpeg_examiner.h +++ b/src/lib/ffmpeg_examiner.h @@ -75,6 +75,12 @@ public: return _rotation; } +#ifdef DCPOMATIC_VARIANT_SWAROOP + boost::optional id () const { + return _id; + } +#endif + private: void video_packet (AVCodecContext *); void audio_packet (AVCodecContext *, boost::shared_ptr); @@ -94,6 +100,10 @@ private: boost::optional _rotation; +#ifdef DCPOMATIC_VARIANT_SWAROOP + boost::optional _id; +#endif + struct SubtitleStart { SubtitleStart (std::string id_, bool image_, dcpomatic::ContentTime time_) diff --git a/src/lib/util.h b/src/lib/util.h index b760f7f4b..b95a0af58 100644 --- a/src/lib/util.h +++ b/src/lib/util.h @@ -63,6 +63,10 @@ namespace dcp { #define CLOSED_CAPTION_LINES 3 /** Maximum line length of closed caption viewers */ #define CLOSED_CAPTION_LENGTH 30 +/* We are mis-using episode_id here, as non-iTunes metadata tags are ignored. + I tried the use_metadata_tags option but it didn't seem to make any difference. +*/ +#define SWAROOP_ID_TAG "episode_id" extern std::string program_name; extern bool is_batch_converter; diff --git a/src/tools/dcpomatic_ecinema.cc b/src/tools/dcpomatic_ecinema.cc index a3f2288e3..6c4b2a8c7 100644 --- a/src/tools/dcpomatic_ecinema.cc +++ b/src/tools/dcpomatic_ecinema.cc @@ -21,6 +21,7 @@ #include "lib/version.h" #include "lib/decrypted_ecinema_kdm.h" #include "lib/config.h" +#include "lib/util.h" #include extern "C" { #include @@ -155,6 +156,15 @@ main (int argc, char* argv[]) dcp::Key key (AES_CTR_KEY_SIZE); AVDictionary* options = 0; av_dict_set (&options, "encryption_key", key.hex().c_str(), 0); + /* XXX: is this OK? */ + av_dict_set (&options, "encryption_kid", "00000000000000000000000000000000", 0); + av_dict_set (&options, "encryption_scheme", "cenc-aes-ctr", 0); + + string id = dcp::make_uuid (); + if (av_dict_set(&output_fc->metadata, SWAROOP_ID_TAG, id.c_str(), 0) < 0) { + cerr << "Could not write ID to output.\n"; + exit (EXIT_FAILURE); + } if (avformat_write_header (output_fc, &options) < 0) { cerr << "Could not write header to output.\n"; @@ -180,7 +190,7 @@ main (int argc, char* argv[]) avformat_free_context (input_fc); avformat_free_context (output_fc); - DecryptedECinemaKDM decrypted_kdm (key); + DecryptedECinemaKDM decrypted_kdm (id, key); EncryptedECinemaKDM encrypted_kdm = decrypted_kdm.encrypt (Config::instance()->decryption_chain()->leaf()); cout << encrypted_kdm.as_xml() << "\n"; } diff --git a/src/tools/dcpomatic_player.cc b/src/tools/dcpomatic_player.cc index 3ba63379b..cd941e935 100644 --- a/src/tools/dcpomatic_player.cc +++ b/src/tools/dcpomatic_player.cc @@ -599,11 +599,27 @@ private: if (d->ShowModal() == wxID_OK) { DCPOMATIC_ASSERT (_film); +#ifdef DCPOMATIC_VARIANT_SWAROOP + shared_ptr ffmpeg = boost::dynamic_pointer_cast(_film->content().front()); + if (ffmpeg) { + try { + ffmpeg->add_kdm (EncryptedECinemaKDM(dcp::file_to_string(wx_to_std(d->GetPath()), MAX_KDM_SIZE))); + } catch (exception& e) { + error_dialog (this, wxString::Format(_("Could not load KDM.")), std_to_wx(e.what())); + d->Destroy(); + return; + } + } +#endif shared_ptr dcp = boost::dynamic_pointer_cast(_film->content().front()); +#ifndef DCPOMATIC_VARIANT_SWAROOP DCPOMATIC_ASSERT (dcp); +#endif try { - dcp->add_kdm (dcp::EncryptedKDM (dcp::file_to_string (wx_to_std (d->GetPath ()), MAX_KDM_SIZE))); - dcp->examine (_film, shared_ptr()); + if (dcp) { + dcp->add_kdm (dcp::EncryptedKDM(dcp::file_to_string(wx_to_std(d->GetPath()), MAX_KDM_SIZE))); + dcp->examine (_film, shared_ptr()); + } } catch (exception& e) { error_dialog (this, wxString::Format (_("Could not load KDM.")), std_to_wx(e.what())); d->Destroy (); diff --git a/src/wx/swaroop_controls.cc b/src/wx/swaroop_controls.cc index d557d09e9..c76ad590a 100644 --- a/src/wx/swaroop_controls.cc +++ b/src/wx/swaroop_controls.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2018 Carl Hetherington + Copyright (C) 2018-2019 Carl Hetherington This file is part of DCP-o-matic. @@ -29,6 +29,7 @@ #include "lib/cross.h" #include "lib/scoped_temporary.h" #include "lib/internet.h" +#include "lib/ffmpeg_content.h" #include #include #include @@ -385,6 +386,28 @@ SwaroopControls::get_kdm_from_directory (shared_ptr dcp) return optional(); } +optional +SwaroopControls::get_kdm_from_directory (shared_ptr ffmpeg) +{ + using namespace boost::filesystem; + optional kdm_dir = Config::instance()->player_kdm_directory(); + if (!kdm_dir) { + return optional(); + } + for (directory_iterator i = directory_iterator(*kdm_dir); i != directory_iterator(); ++i) { + try { + if (file_size(i->path()) < MAX_KDM_SIZE) { + EncryptedECinemaKDM kdm (dcp::file_to_string(i->path())); + if (kdm.id() == ffmpeg->id().get_value_or("")) { + return kdm; + } + } + } catch (std::exception& e) { + /* Hey well */ + } + } + return optional(); +} void SwaroopControls::spl_selection_changed () { @@ -442,6 +465,22 @@ SwaroopControls::select_playlist (int selected, int position) return; } } + shared_ptr ffmpeg = dynamic_pointer_cast (i.content); + if (ffmpeg && ffmpeg->encrypted()) { + optional kdm = get_kdm_from_directory (ffmpeg); + if (kdm) { + try { + ffmpeg->add_kdm (*kdm); + } catch (KDMError& e) { + error_dialog (this, "Could not load KDM."); + } + } else { + error_dialog (this, "This playlist cannot be loaded as a KDM is missing."); + _selected_playlist = boost::none; + _spl_view->SetItemState (selected, 0, wxLIST_STATE_SELECTED); + return; + } + } } _current_spl_view->DeleteAllItems (); diff --git a/src/wx/swaroop_controls.h b/src/wx/swaroop_controls.h index 8400d8cdb..1f740d228 100644 --- a/src/wx/swaroop_controls.h +++ b/src/wx/swaroop_controls.h @@ -21,6 +21,7 @@ #include "controls.h" class DCPContent; +class EncryptedECinemaKDM; class SwaroopControls : public Controls { @@ -61,6 +62,7 @@ private: boost::optional get_kdm_from_url (boost::shared_ptr dcp); boost::optional get_kdm_from_directory (boost::shared_ptr dcp); + boost::optional get_kdm_from_directory (boost::shared_ptr ffmpeg); wxButton* _play_button; wxButton* _pause_button; -- 2.30.2