summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2019-05-13 16:08:33 +0100
committerCarl Hetherington <cth@carlh.net>2019-05-13 16:08:33 +0100
commit257fce15e8b4dfa112d039e7888e3ec04e523198 (patch)
tree3afe5047010bb71aaa3851ef3b2f438509bccad3
parent5de2fd90b92829cea8ab297c6fce81c582332cb7 (diff)
swaroop: basics of encrypted MP4 playback.
-rw-r--r--src/lib/decrypted_ecinema_kdm.cc38
-rw-r--r--src/lib/decrypted_ecinema_kdm.h12
-rw-r--r--src/lib/encrypted_ecinema_kdm.cc18
-rw-r--r--src/lib/encrypted_ecinema_kdm.h12
-rw-r--r--src/lib/ffmpeg.cc9
-rw-r--r--src/lib/ffmpeg_content.cc20
-rw-r--r--src/lib/ffmpeg_content.h26
-rw-r--r--src/lib/ffmpeg_decoder.cc7
-rw-r--r--src/lib/ffmpeg_examiner.cc7
-rw-r--r--src/lib/ffmpeg_examiner.h10
-rw-r--r--src/lib/util.h4
-rw-r--r--src/tools/dcpomatic_ecinema.cc12
-rw-r--r--src/tools/dcpomatic_player.cc20
-rw-r--r--src/wx/swaroop_controls.cc41
-rw-r--r--src/wx/swaroop_controls.h2
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 <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
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 <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");
}
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 <cth@carlh.net>
+ Copyright (C) 2013-2019 Carl Hetherington <cth@carlh.net>
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 <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);
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<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
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<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
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<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
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<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_)
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 <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";
}
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<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 ();
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 <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 ();
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<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;