swaroop: support validity periods in ecinema KDMs.
authorCarl Hetherington <cth@carlh.net>
Thu, 6 Jun 2019 21:10:40 +0000 (22:10 +0100)
committerCarl Hetherington <cth@carlh.net>
Sun, 9 Jun 2019 21:33:49 +0000 (22:33 +0100)
12 files changed:
src/lib/decrypted_ecinema_kdm.cc
src/lib/decrypted_ecinema_kdm.h
src/lib/ecinema_kdm_data.h [new file with mode: 0644]
src/lib/encrypted_ecinema_kdm.cc
src/lib/encrypted_ecinema_kdm.h
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/tools/dcpomatic_ecinema.cc
src/tools/dcpomatic_kdm.cc
src/tools/dcpomatic_player.cc
test/ecinema_kdm_test.cc [new file with mode: 0644]
test/wscript

index 14843185fdbba008de2a56f508d0802e6f397317..f76161c8b6a2dec461040913abc94a8d320b0df0 100644 (file)
@@ -22,7 +22,9 @@
 
 #include "encrypted_ecinema_kdm.h"
 #include "decrypted_ecinema_kdm.h"
+#include "ecinema_kdm_data.h"
 #include "exceptions.h"
+#include "compose.hpp"
 #include <dcp/key.h>
 #include <dcp/util.h>
 #include <dcp/certificate.h>
 using std::string;
 using std::runtime_error;
 using dcp::Certificate;
+using boost::optional;
 
-DecryptedECinemaKDM::DecryptedECinemaKDM (string id, string name, dcp::Key content_key)
+DecryptedECinemaKDM::DecryptedECinemaKDM (string id, string name, dcp::Key content_key, optional<dcp::LocalTime> not_valid_before, optional<dcp::LocalTime> not_valid_after)
        : _id (id)
        , _name (name)
        , _content_key (content_key)
+       , _not_valid_before (not_valid_before)
+       , _not_valid_after (not_valid_after)
 {
 
 }
@@ -59,18 +64,38 @@ DecryptedECinemaKDM::DecryptedECinemaKDM (EncryptedECinemaKDM kdm, string privat
        }
 
        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);
+       int const len = RSA_private_decrypt (kdm.data().size(), kdm.data().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);
+       if (len != ECINEMA_KDM_KEY_LENGTH && len != (ECINEMA_KDM_KEY_LENGTH + ECINEMA_KDM_NOT_VALID_BEFORE_LENGTH + ECINEMA_KDM_NOT_VALID_AFTER_LENGTH)) {
+               throw KDMError (
+                       "Unexpected data block size in ECinema KDM.",
+                       String::compose("Size was %1; expected %2 or %3", ECINEMA_KDM_KEY_LENGTH, ECINEMA_KDM_KEY_LENGTH + ECINEMA_KDM_NOT_VALID_BEFORE_LENGTH + ECINEMA_KDM_NOT_VALID_AFTER_LENGTH)
+                       );
+       }
+
+       _content_key = dcp::Key (value + ECINEMA_KDM_KEY, ECINEMA_KDM_KEY_LENGTH);
+       if (len > ECINEMA_KDM_KEY_LENGTH) {
+               uint8_t* p = value + ECINEMA_KDM_NOT_VALID_BEFORE;
+               string b;
+               for (int i = 0; i < ECINEMA_KDM_NOT_VALID_BEFORE_LENGTH; ++i) {
+                       b += *p++;
+               }
+               _not_valid_before = dcp::LocalTime (b);
+               string a;
+               for (int i = 0; i < ECINEMA_KDM_NOT_VALID_AFTER_LENGTH; ++i) {
+                       a += *p++;
+               }
+               _not_valid_after = dcp::LocalTime (a);
+       }
 }
 
 EncryptedECinemaKDM
 DecryptedECinemaKDM::encrypt (Certificate recipient)
 {
-       return EncryptedECinemaKDM (_id, _name, _content_key, recipient);
+       return EncryptedECinemaKDM (_id, _name, _content_key, _not_valid_before, _not_valid_after, recipient);
 }
 
 #endif
index 0ddc256154466fe8f2f13e60d7d5c1eb101cb363..7a66b63e09dbfa32d994ce5bba9804d5436784ef 100644 (file)
@@ -28,7 +28,7 @@
 class DecryptedECinemaKDM
 {
 public:
-       DecryptedECinemaKDM (std::string id, std::string name, dcp::Key content_key);
+       DecryptedECinemaKDM (std::string id, std::string name, dcp::Key content_key, boost::optional<dcp::LocalTime> not_valid_before, boost::optional<dcp::LocalTime> not_valid_after);
        DecryptedECinemaKDM (EncryptedECinemaKDM kdm, std::string private_key);
 
        EncryptedECinemaKDM encrypt (dcp::Certificate recipient);
@@ -45,11 +45,21 @@ public:
                return _content_key;
        }
 
+       boost::optional<dcp::LocalTime> not_valid_before () const {
+               return _not_valid_before;
+       }
+
+       boost::optional<dcp::LocalTime> not_valid_after () const {
+               return _not_valid_after;
+       }
+
 private:
        std::string _id;
        std::string _name;
        /** unenecrypted content key */
        dcp::Key _content_key;
+       boost::optional<dcp::LocalTime> _not_valid_before;
+       boost::optional<dcp::LocalTime> _not_valid_after;
 };
 
 #endif
diff --git a/src/lib/ecinema_kdm_data.h b/src/lib/ecinema_kdm_data.h
new file mode 100644 (file)
index 0000000..9ca3b24
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+    Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+/* ECinema KDM data block contains:
+   - key (16 bytes)
+   - (optional) not-valid-before time (25 bytes)
+   - (optional) not-valid-after time  (25 bytes)
+*/
+
+#define ECINEMA_KDM_KEY 0
+#define ECINEMA_KDM_KEY_LENGTH 16
+#define ECINEMA_KDM_NOT_VALID_BEFORE (ECINEMA_KDM_KEY_LENGTH)
+#define ECINEMA_KDM_NOT_VALID_BEFORE_LENGTH 25
+#define ECINEMA_KDM_NOT_VALID_AFTER (ECINEMA_KDM_NOT_VALID_BEFORE + ECINEMA_KDM_NOT_VALID_BEFORE_LENGTH)
+#define ECINEMA_KDM_NOT_VALID_AFTER_LENGTH 25
index f0502ab31ddde720d1e50f57dd7bfd444a79e2ac..faea034249a5d8c9294737aad8f408182376db4f 100644 (file)
@@ -21,6 +21,7 @@
 #ifdef DCPOMATIC_VARIANT_SWAROOP
 
 #include "encrypted_ecinema_kdm.h"
+#include "ecinema_kdm_data.h"
 #include "exceptions.h"
 #include "cross.h"
 #include <dcp/key.h>
 using std::cout;
 using std::string;
 using boost::shared_ptr;
+using boost::optional;
 using dcp::Certificate;
 
-EncryptedECinemaKDM::EncryptedECinemaKDM (string id, string name, dcp::Key content_key, Certificate recipient)
+EncryptedECinemaKDM::EncryptedECinemaKDM (string id, string name, dcp::Key content_key, optional<dcp::LocalTime> not_valid_before, optional<dcp::LocalTime> not_valid_after, Certificate recipient)
        : _id (id)
        , _name (name)
 {
        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);
+       _data = dcp::Data (RSA_size(rsa));
+
+       int input_size = ECINEMA_KDM_KEY_LENGTH;
+       if (not_valid_before && not_valid_after) {
+               input_size += ECINEMA_KDM_NOT_VALID_BEFORE_LENGTH + ECINEMA_KDM_NOT_VALID_AFTER_LENGTH;
+       }
+
+       dcp::Data input (input_size);
+       memcpy (input.data().get(), content_key.value(), ECINEMA_KDM_KEY_LENGTH);
+       if (not_valid_before && not_valid_after) {
+               memcpy (input.data().get() + ECINEMA_KDM_NOT_VALID_BEFORE, not_valid_before->as_string().c_str(), ECINEMA_KDM_NOT_VALID_BEFORE_LENGTH);
+               memcpy (input.data().get() + ECINEMA_KDM_NOT_VALID_AFTER, not_valid_after->as_string().c_str(), ECINEMA_KDM_NOT_VALID_AFTER_LENGTH);
+       }
+
+       int const N = RSA_public_encrypt (input_size, input.data().get(), _data.data().get(), rsa, RSA_PKCS1_OAEP_PADDING);
        if (N == -1) {
                throw KDMError ("Could not encrypt ECinema KDM", ERR_error_string(ERR_get_error(), 0));
        }
@@ -56,9 +71,9 @@ EncryptedECinemaKDM::EncryptedECinemaKDM (string xml)
        doc.read_string (xml);
        _id = doc.string_child ("Id");
        _name = doc.string_child ("Name");
-       _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);
+       _data = dcp::Data (256);
+       int const len = dcp::base64_decode (doc.string_child("Data"), _data.data().get(), _data.size());
+       _data.set_size (len);
 }
 
 string
@@ -67,8 +82,8 @@ EncryptedECinemaKDM::as_xml () const
        string key;
 
        /* Lazy overallocation */
-       char out[_content_key.size() * 2];
-       Kumu::base64encode (_content_key.data().get(), _content_key.size(), out, _content_key.size() * 2);
+       char out[_data.size() * 2];
+       Kumu::base64encode (_data.data().get(), _data.size(), out, _data.size() * 2);
        int const N = strlen (out);
        string lines;
        for (int i = 0; i < N; ++i) {
@@ -82,7 +97,7 @@ EncryptedECinemaKDM::as_xml () const
        xmlpp::Element* root = document.create_root_node ("ECinemaSecurityMessage");
        root->add_child("Id")->add_child_text(_id);
        root->add_child("Name")->add_child_text(_name);
-       root->add_child("Key")->add_child_text(lines);
+       root->add_child("Data")->add_child_text(lines);
        return document.write_to_string ("UTF-8");
 }
 
index c65620c8b2970de2a8a4529e37eb1fe458725f1e..90e13422a40621bdffe43c6be2477b81badbb038 100644 (file)
@@ -26,6 +26,8 @@
 #include <dcp/key.h>
 #include <dcp/data.h>
 #include <dcp/certificate.h>
+#include <dcp/local_time.h>
+#include <boost/optional.hpp>
 
 class DecryptedECinemaKDM;
 
@@ -45,19 +47,19 @@ public:
                return _name;
        }
 
-       dcp::Data key () const {
-               return _content_key;
+       dcp::Data data () const {
+               return _data;
        }
 
 private:
        friend class DecryptedECinemaKDM;
 
-       EncryptedECinemaKDM (std::string id, std::string name, dcp::Key key, dcp::Certificate recipient);
+       EncryptedECinemaKDM (std::string id, std::string name, dcp::Key key, boost::optional<dcp::LocalTime> not_valid_before, boost::optional<dcp::LocalTime> not_valid_after, dcp::Certificate recipient);
 
        std::string _id;
        std::string _name;
-       /** encrypted content key */
-       dcp::Data _content_key;
+       /** encrypted data */
+       dcp::Data _data;
 };
 
 #endif
index 29ff7e80baeb3f5550f28b674122121f230d381a..f3c0d01cbafc792eb9356ae6b484a6e4e420a1ea 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 "filter.h"
 #include "film.h"
 #include "log.h"
+#include "config.h"
 #include "exceptions.h"
 #include "frame_rate_change.h"
 #include "text_content.h"
+#include "decrypted_ecinema_kdm.h"
 #include <dcp/raw_convert.h>
 #include <libcxml/cxml.h>
 extern "C" {
@@ -710,4 +712,21 @@ FFmpegContent::add_kdm (EncryptedECinemaKDM kdm)
        _kdm = kdm;
 
 }
+
+bool
+FFmpegContent::kdm_timing_window_valid () const
+{
+       if (!_kdm) {
+               return true;
+       }
+
+       DCPOMATIC_ASSERT (Config::instance()->decryption_chain()->key());
+
+       DecryptedECinemaKDM decrypted (*_kdm, *Config::instance()->decryption_chain()->key());
+
+       dcp::LocalTime now;
+       return (!decrypted.not_valid_before() || *decrypted.not_valid_before() < now) &&
+               (!decrypted.not_valid_after() || now < *decrypted.not_valid_after());
+}
+
 #endif
index 6c572f242eae9ca7d004342c4f2441bd573291a9..cf1849971450659f5d12ccb64d0b1c681a825561 100644 (file)
@@ -119,6 +119,8 @@ public:
                return _id;
        }
 
+       bool kdm_timing_window_valid () const;
+
 #endif
 
 private:
index 740893fa9fdc78fa0aec6110734b0cfa5a403c5b..719f7d4dddd10a8118c5139815dd11c5741c3188 100644 (file)
@@ -210,7 +210,7 @@ main (int argc, char* argv[])
        avformat_free_context (input_fc);
        avformat_free_context (output_fc);
 
-       DecryptedECinemaKDM decrypted_kdm (id, output_file.filename().string(), key);
+       DecryptedECinemaKDM decrypted_kdm (id, output_file.filename().string(), key, optional<dcp::LocalTime>(), optional<dcp::LocalTime>());
        EncryptedECinemaKDM encrypted_kdm = decrypted_kdm.encrypt (Config::instance()->decryption_chain()->leaf());
        cout << encrypted_kdm.as_xml() << "\n";
 }
index 9f1c28c990f9992f1a42a297313bb5a557c27fb2..ef6b783f4fa0045bb753b2fdfc8f4aefe1509d80 100644 (file)
@@ -318,7 +318,13 @@ private:
                                                continue;
                                        }
 
-                                       DecryptedECinemaKDM kdm (decrypted.id(), decrypted.name(), decrypted.key());
+                                       DecryptedECinemaKDM kdm (
+                                               decrypted.id(),
+                                               decrypted.name(),
+                                               decrypted.key(),
+                                               dcp::LocalTime (_timing->from(), i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()),
+                                               dcp::LocalTime (_timing->until(), i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute())
+                                               );
 
                                        /* Encrypt */
                                        screen_kdms.push_back (
index 6a64bd0ded4f26d80492b9fd3505c472671425b5..90cc818ca5d85271001c576ce0df8c5a033c37ad 100644 (file)
@@ -287,6 +287,15 @@ public:
                        }
                }
 
+#ifdef DCPOMATIC_VARIANT_SWAROOP
+               BOOST_FOREACH (shared_ptr<Content> i, _film->content()) {
+                       shared_ptr<FFmpegContent> c = dynamic_pointer_cast<FFmpegContent>(i);
+                       if (c && !c->kdm_timing_window_valid()) {
+                               ok = false;
+                       }
+               }
+#endif
+
                if (!ok) {
                        error_dialog (this, _("The KDM does not allow playback of this content at this time."));
                }
diff --git a/test/ecinema_kdm_test.cc b/test/ecinema_kdm_test.cc
new file mode 100644 (file)
index 0000000..e44a626
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+    Copyright (C) 2019 Carl Hetherington <cth@carlh.net>
+
+    This file is part of DCP-o-matic.
+
+    DCP-o-matic is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    DCP-o-matic 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "lib/decrypted_ecinema_kdm.h"
+#include "lib/encrypted_ecinema_kdm.h"
+#include "lib/config.h"
+#include <boost/test/unit_test.hpp>
+extern "C" {
+#include <libavutil/aes_ctr.h>
+}
+#include <fstream>
+
+using std::string;
+using boost::optional;
+
+#ifdef DCPOMATIC_VARIANT_SWAROOP
+
+BOOST_AUTO_TEST_CASE (ecinema_kdm_roundtrip_test1)
+{
+       dcp::Key key (AES_CTR_KEY_SIZE);
+       DecryptedECinemaKDM dec ("123-456-789-0", "Hello world", key, optional<dcp::LocalTime>(), optional<dcp::LocalTime>());
+       EncryptedECinemaKDM enc = dec.encrypt (Config::instance()->decryption_chain()->leaf());
+       DecryptedECinemaKDM dec2 (enc, *Config::instance()->decryption_chain()->key());
+       BOOST_CHECK_EQUAL (dec2.id(), "123-456-789-0");
+       BOOST_CHECK_EQUAL (dec2.name(), "Hello world");
+       BOOST_CHECK (dec2.key() == key);
+       BOOST_CHECK (!static_cast<bool>(dec2.not_valid_before()));
+       BOOST_CHECK (!static_cast<bool>(dec2.not_valid_after()));
+}
+
+BOOST_AUTO_TEST_CASE (ecinema_kdm_roundtrip_test2)
+{
+       dcp::Key key (AES_CTR_KEY_SIZE);
+       DecryptedECinemaKDM dec ("123-456-789-0", "Hello world", key, dcp::LocalTime("2019-06-01T15:05:23+01:00"), dcp::LocalTime("2019-07-02T19:10:12+02:00"));
+       EncryptedECinemaKDM enc = dec.encrypt (Config::instance()->decryption_chain()->leaf());
+       DecryptedECinemaKDM dec2 (enc, *Config::instance()->decryption_chain()->key());
+       BOOST_CHECK_EQUAL (dec2.id(), "123-456-789-0");
+       BOOST_CHECK_EQUAL (dec2.name(), "Hello world");
+       BOOST_CHECK (dec2.key() == key);
+       BOOST_REQUIRE (static_cast<bool>(dec2.not_valid_before()));
+       BOOST_CHECK_EQUAL (dec2.not_valid_before()->as_string(), "2019-06-01T15:05:23+01:00");
+       BOOST_REQUIRE (static_cast<bool>(dec2.not_valid_after()));
+       BOOST_CHECK_EQUAL (dec2.not_valid_after()->as_string(), "2019-07-02T19:10:12+02:00");
+}
+
+BOOST_AUTO_TEST_CASE (ecinema_kdm_roundtrip_test3)
+{
+       dcp::Key key (AES_CTR_KEY_SIZE);
+       DecryptedECinemaKDM dec ("123-456-789-0", "Hello world", key, optional<dcp::LocalTime>(), optional<dcp::LocalTime>());
+       EncryptedECinemaKDM enc = dec.encrypt (Config::instance()->decryption_chain()->leaf());
+       string const enc_xml = enc.as_xml ();
+       EncryptedECinemaKDM enc2 (enc_xml);
+       DecryptedECinemaKDM dec2 (enc2, *Config::instance()->decryption_chain()->key());
+       BOOST_CHECK_EQUAL (dec2.id(), "123-456-789-0");
+       BOOST_CHECK_EQUAL (dec2.name(), "Hello world");
+       BOOST_CHECK (dec2.key() == key);
+       BOOST_CHECK (!static_cast<bool>(dec2.not_valid_before()));
+       BOOST_CHECK (!static_cast<bool>(dec2.not_valid_after()));
+}
+
+BOOST_AUTO_TEST_CASE (ecinema_kdm_roundtrip_test4)
+{
+       dcp::Key key (AES_CTR_KEY_SIZE);
+       DecryptedECinemaKDM dec ("123-456-789-0", "Hello world", key, dcp::LocalTime("2019-06-01T15:05:23+01:00"), dcp::LocalTime("2019-07-02T19:10:12+02:00"));
+       EncryptedECinemaKDM enc = dec.encrypt (Config::instance()->decryption_chain()->leaf());
+       string const enc_xml = enc.as_xml();
+       EncryptedECinemaKDM enc2 (dcp::file_to_string("build/test/shit_the_bed.xml"));
+       DecryptedECinemaKDM dec2 (enc2, *Config::instance()->decryption_chain()->key());
+       BOOST_CHECK_EQUAL (dec2.id(), "123-456-789-0");
+       BOOST_CHECK_EQUAL (dec2.name(), "Hello world");
+       BOOST_CHECK (dec2.key() == key);
+       BOOST_REQUIRE (static_cast<bool>(dec2.not_valid_before()));
+       BOOST_CHECK_EQUAL (dec2.not_valid_before()->as_string(), "2019-06-01T15:05:23+01:00");
+       BOOST_REQUIRE (static_cast<bool>(dec2.not_valid_after()));
+       BOOST_CHECK_EQUAL (dec2.not_valid_after()->as_string(), "2019-07-02T19:10:12+02:00");
+}
+
+#endif
index 8059ae2d35244d9aa0b003eb98afc99c309dd40d..c0ce3196b791b99909f2a0cca64e976ae733fdd2 100644 (file)
@@ -63,6 +63,7 @@ def build(bld):
                  dcp_playback_test.cc
                  dcp_subtitle_test.cc
                  digest_test.cc
+                 ecinema_kdm_test.cc
                  empty_test.cc
                  ffmpeg_audio_only_test.cc
                  ffmpeg_audio_test.cc