swaroop: basics of encrypted MP4 playback.
authorCarl Hetherington <cth@carlh.net>
Mon, 13 May 2019 15:08:33 +0000 (16:08 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 13 May 2019 15:08:33 +0000 (16:08 +0100)
15 files changed:
src/lib/decrypted_ecinema_kdm.cc
src/lib/decrypted_ecinema_kdm.h
src/lib/encrypted_ecinema_kdm.cc
src/lib/encrypted_ecinema_kdm.h
src/lib/ffmpeg.cc
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/ffmpeg_decoder.cc
src/lib/ffmpeg_examiner.cc
src/lib/ffmpeg_examiner.h
src/lib/util.h
src/tools/dcpomatic_ecinema.cc
src/tools/dcpomatic_player.cc
src/wx/swaroop_controls.cc
src/wx/swaroop_controls.h

index 2cb4f61d7a3ece135d9894ae2e3ae756358d18fd..a03004e4378eb9c7ef9355e24e66a06e008ff842 100644 (file)
 
 #include "encrypted_ecinema_kdm.h"
 #include "decrypted_ecinema_kdm.h"
+#include "exceptions.h"
 #include <dcp/key.h>
+#include <dcp/util.h>
 #include <dcp/certificate.h>
+#include <openssl/rsa.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
 
+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<char *> (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
index b0fc2064d65b8c42b0d4af0421be87d1ed73bf0b..f61402b7bffab17f5638741197bc361f5e4436ae 100644 (file)
 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;
 };
index e277eb997c46602b34e116f6d7ada4b544d21b55..ab9e15e858d8a1bab6851c3620a6b2bff108b780 100644 (file)
@@ -23,6 +23,8 @@
 #include "encrypted_ecinema_kdm.h"
 #include <dcp/key.h>
 #include <dcp/certificate.h>
+#include <dcp/util.h>
+#include <libcxml/cxml.h>
 #include <libxml++/libxml++.h>
 #include <openssl/rsa.h>
 #include <iostream>
@@ -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");
 }
index fc6fbdb655c9e08dab372edc666231f389fd4b9f..ece1e3161b8ab003498ad3788a4c878055a3ed8e 100644 (file)
@@ -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;
 };
index ab5148cfa0295e79f8e838282dc943974989b4db..d6a592f8818ad5b6b6c91bd5cf1e0f1ca4834d57 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2013-2019 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 #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 <dcp/raw_convert.h>
 extern "C" {
 #include <libavcodec/avcodec.h>
@@ -123,8 +125,9 @@ FFmpeg::setup_general ()
        */
        av_dict_set (&options, "analyzeduration", raw_convert<string> (5 * 60 * 1000000).c_str(), 0);
        av_dict_set (&options, "probesize", raw_convert<string> (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);
index 69d74321514729208eaeea371185a924b52f80fe..e291b8e7cb151b839969f61a1daa18468e42fa49 100644 (file)
@@ -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<string>
        _color_trc = get_optional_enum<AVColorTransferCharacteristic>(node, "ColorTransferCharacteristic");
        _colorspace = get_optional_enum<AVColorSpace>(node, "Colorspace");
        _bits_per_pixel = node->optional_number_child<int> ("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<string> (*_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<const Film> film, shared_ptr<Job> 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<const Content> c)
        Content::take_settings_from (c);
        _filters = fc->_filters;
 }
+
+#ifdef DCPOMATIC_VARIANT_SWAROOP
+void
+FFmpegContent::add_kdm (EncryptedECinemaKDM kdm)
+{
+       ChangeSignaller<Content> cc (this, FFmpegContentProperty::KDM);
+       boost::mutex::scoped_lock lm (_mutex);
+       _kdm = kdm;
+
+}
+#endif
index 8871301b16ae900d10c91b7854393bb1e8eef251..6c572f242eae9ca7d004342c4f2441bd573291a9 100644 (file)
@@ -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<std::string> 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<EncryptedECinemaKDM> kdm () const {
+               return _kdm;
+       }
+
+       boost::optional<std::string> id () const {
+               return _id;
+       }
+
+#endif
+
 private:
        void add_properties (boost::shared_ptr<const Film> film, std::list<UserProperty> &) const;
 
@@ -125,8 +138,11 @@ private:
        boost::optional<AVColorTransferCharacteristic> _color_trc;
        boost::optional<AVColorSpace> _colorspace;
        boost::optional<int> _bits_per_pixel;
-       boost::optional<std::string> _decryption_key;
+#ifdef DCPOMATIC_VARIANT_SWAROOP
        bool _encrypted;
+       boost::optional<EncryptedECinemaKDM> _kdm;
+       boost::optional<std::string> _id;
+#endif
 };
 
 #endif
index 0e65a6d6b272a67e2f9bdc37bb281f8114684d87..c52723da3edb051ff09d042df9b80c500abce919 100644 (file)
@@ -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
index 382b71b83442a36e2fe4910310131190fbfcb84f..a4c5eb1280060c1b7247c9bed2c5de080867cd86 100644 (file)
@@ -166,6 +166,13 @@ FFmpegExaminer::FFmpegExaminer (shared_ptr<const FFmpegContent> c, shared_ptr<Jo
 
                DCPOMATIC_ASSERT (fabs (*_rotation - 90 * round (*_rotation / 90)) < 2);
        }
+
+#ifdef DCPOMATIC_VARIANT_SWAROOP
+       AVDictionaryEntry* e = av_dict_get (_format_context->metadata, SWAROOP_ID_TAG, 0, 0);
+       if (e) {
+               _id = e->value;
+       }
+#endif
 }
 
 void
index d2e6e1a0a1da1dbd17e9778d2058a8edb389d0d4..1c0dad3dc6fc15eb324f61c61b3324e4fcec0893 100644 (file)
@@ -75,6 +75,12 @@ public:
                return _rotation;
        }
 
+#ifdef DCPOMATIC_VARIANT_SWAROOP
+       boost::optional<std::string> id () const {
+               return _id;
+       }
+#endif
+
 private:
        void video_packet (AVCodecContext *);
        void audio_packet (AVCodecContext *, boost::shared_ptr<FFmpegAudioStream>);
@@ -94,6 +100,10 @@ private:
 
        boost::optional<double> _rotation;
 
+#ifdef DCPOMATIC_VARIANT_SWAROOP
+       boost::optional<std::string> _id;
+#endif
+
        struct SubtitleStart
        {
                SubtitleStart (std::string id_, bool image_, dcpomatic::ContentTime time_)
index b760f7f4bc12f7dc35ecae5057b8e034d757ae03..b95a0af58498a01632f214a1a1814753735865eb 100644 (file)
@@ -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;
index a3f2288e3ff1533c8cf49aee4343450de2b07abe..6c4b2a8c75808956209dfd05c36fc2599c7e3220 100644 (file)
@@ -21,6 +21,7 @@
 #include "lib/version.h"
 #include "lib/decrypted_ecinema_kdm.h"
 #include "lib/config.h"
+#include "lib/util.h"
 #include <dcp/key.h>
 extern "C" {
 #include <libavformat/avformat.h>
@@ -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";
 }
index 3ba63379bbbecb820c3ad30fd82aeff1fa4fff13..cd941e93557deab2cc53344204f5022fdc88dcd5 100644 (file)
@@ -599,11 +599,27 @@ private:
 
                if (d->ShowModal() == wxID_OK) {
                        DCPOMATIC_ASSERT (_film);
+#ifdef DCPOMATIC_VARIANT_SWAROOP
+                       shared_ptr<FFmpegContent> ffmpeg = boost::dynamic_pointer_cast<FFmpegContent>(_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<DCPContent> dcp = boost::dynamic_pointer_cast<DCPContent>(_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<Job>());
+                               if (dcp) {
+                                       dcp->add_kdm (dcp::EncryptedKDM(dcp::file_to_string(wx_to_std(d->GetPath()), MAX_KDM_SIZE)));
+                                       dcp->examine (_film, shared_ptr<Job>());
+                               }
                        } catch (exception& e) {
                                error_dialog (this, wxString::Format (_("Could not load KDM.")), std_to_wx(e.what()));
                                d->Destroy ();
index d557d09e9b8fe9190c7d7882fd455ccbcfaa8f2d..c76ad590a21fb4c6abbc09f7c34e854cae55efac 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2018 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2018-2019 Carl Hetherington <cth@carlh.net>
 
     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 <dcp/raw_convert.h>
 #include <dcp/exceptions.h>
 #include <wx/listctrl.h>
@@ -385,6 +386,28 @@ SwaroopControls::get_kdm_from_directory (shared_ptr<DCPContent> dcp)
        return optional<dcp::EncryptedKDM>();
 }
 
+optional<EncryptedECinemaKDM>
+SwaroopControls::get_kdm_from_directory (shared_ptr<FFmpegContent> ffmpeg)
+{
+       using namespace boost::filesystem;
+       optional<path> kdm_dir = Config::instance()->player_kdm_directory();
+       if (!kdm_dir) {
+               return optional<EncryptedECinemaKDM>();
+       }
+       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<EncryptedECinemaKDM>();
+}
 void
 SwaroopControls::spl_selection_changed ()
 {
@@ -442,6 +465,22 @@ SwaroopControls::select_playlist (int selected, int position)
                                return;
                        }
                }
+               shared_ptr<FFmpegContent> ffmpeg = dynamic_pointer_cast<FFmpegContent> (i.content);
+               if (ffmpeg && ffmpeg->encrypted()) {
+                       optional<EncryptedECinemaKDM> 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 ();
index 8400d8cdb573e036cf3314866a783db8fea018ca..1f740d2283825c7164212e1d954adce40ff71f33 100644 (file)
@@ -21,6 +21,7 @@
 #include "controls.h"
 
 class DCPContent;
+class EncryptedECinemaKDM;
 
 class SwaroopControls : public Controls
 {
@@ -61,6 +62,7 @@ private:
 
        boost::optional<dcp::EncryptedKDM> get_kdm_from_url (boost::shared_ptr<DCPContent> dcp);
        boost::optional<dcp::EncryptedKDM> get_kdm_from_directory (boost::shared_ptr<DCPContent> dcp);
+       boost::optional<EncryptedECinemaKDM> get_kdm_from_directory (boost::shared_ptr<FFmpegContent> ffmpeg);
 
        wxButton* _play_button;
        wxButton* _pause_button;