Warn/error on making KDMs using recipient certs whose validity periods
authorCarl Hetherington <cth@carlh.net>
Thu, 19 Jan 2023 21:03:21 +0000 (22:03 +0100)
committerCarl Hetherington <cth@carlh.net>
Sat, 11 Feb 2023 21:26:38 +0000 (22:26 +0100)
lie outside those of the KDMs (#2423).

src/lib/kdm_cli.cc
src/lib/kdm_util.cc [new file with mode: 0644]
src/lib/kdm_util.h [new file with mode: 0644]
src/lib/screen.cc
src/lib/screen.h
src/lib/wscript
src/tools/dcpomatic_kdm.cc
src/wx/kdm_dialog.cc
test/kdm_naming_test.cc
test/kdm_util_test.cc [new file with mode: 0644]
test/wscript

index a76155a2c8d8a4bd6ad17bbe9f508e5fb7561c59..dc74e716155357e619a89b61ad3da08b4bdf34eb 100644 (file)
@@ -240,14 +240,28 @@ from_film (
 
        auto cpl = cpls.front().cpl_file;
 
+       std::vector<KDMCertificatePeriod> period_checks;
+
        try {
                list<KDMWithMetadataPtr> kdms;
                for (auto i: screens) {
-                       auto p = kdm_for_screen (film, cpl, i, valid_from, valid_to, formulation, disable_forensic_marking_picture, disable_forensic_marking_audio);
+                       auto p = kdm_for_screen(film, cpl, i, valid_from, valid_to, formulation, disable_forensic_marking_picture, disable_forensic_marking_audio, period_checks);
                        if (p) {
                                kdms.push_back (p);
                        }
                }
+
+
+               if (find(period_checks.begin(), period_checks.end(), KDMCertificatePeriod::KDM_OUTSIDE_CERTIFICATE) != period_checks.end()) {
+                       throw KDMCLIError(
+                               "Some KDMs would have validity periods which are completely outside the recipient certificate periods.  Such KDMs are very unlikely to work, so will not be created."
+                               );
+               }
+
+               if (find(period_checks.begin(), period_checks.end(), KDMCertificatePeriod::KDM_OVERLAPS_CERTIFICATE) != period_checks.end()) {
+                       out("For some of these KDMs the recipient certificate's validity period will not cover the whole of the KDM validity period.  This might cause problems with the KDMs.");
+               }
+
                write_files (kdms, zip, output, container_name_format, filename_format, verbose, out);
                if (email) {
                        send_emails ({kdms}, container_name_format, filename_format, film->dcp_name(), {});
diff --git a/src/lib/kdm_util.cc b/src/lib/kdm_util.cc
new file mode 100644 (file)
index 0000000..bf112ce
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+    Copyright (C) 2023 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 "kdm_util.h"
+#include "screen.h"
+#include <dcp/certificate.h>
+#include <boost/optional.hpp>
+
+#include "i18n.h"
+
+
+using std::list;
+using std::pair;
+using std::shared_ptr;
+using std::string;
+using boost::optional;
+
+
+KDMCertificatePeriod
+check_kdm_and_certificate_validity_periods(dcp::Certificate const& recipient, dcp::LocalTime kdm_from, dcp::LocalTime kdm_to)
+{
+       auto overlaps = [](dcp::LocalTime from_a, dcp::LocalTime to_a, dcp::LocalTime from_b, dcp::LocalTime to_b) {
+               return std::max(from_a, from_b) < std::min(to_a, to_b);
+       };
+
+       auto contains = [](dcp::LocalTime bigger_from, dcp::LocalTime bigger_to, dcp::LocalTime smaller_from, dcp::LocalTime smaller_to) {
+               return bigger_from <= smaller_from && bigger_to >= smaller_to;
+       };
+
+       if (contains(recipient.not_before(), recipient.not_after(), kdm_from, kdm_to)) {
+               return KDMCertificatePeriod::KDM_WITHIN_CERTIFICATE;
+       }
+
+       if (overlaps(recipient.not_before(), recipient.not_after(), kdm_from, kdm_to)) {
+               /* The KDM overlaps the certificate validity: maybe not the end of the world */
+               return KDMCertificatePeriod::KDM_OVERLAPS_CERTIFICATE;
+       } else {
+               /* The KDM validity is totally outside the certificate validity: bad news */
+               return KDMCertificatePeriod::KDM_OUTSIDE_CERTIFICATE;
+       }
+}
+
diff --git a/src/lib/kdm_util.h b/src/lib/kdm_util.h
new file mode 100644 (file)
index 0000000..4baa0c6
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+    Copyright (C) 2023 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/>.
+
+*/
+
+
+#ifndef DCPOMATIC_KDM_UTIL_H
+#define DCPOMATIC_KDM_UTIL_H
+
+
+#include <utility>
+#include <dcp/local_time.h>
+
+
+namespace dcp {
+       class Certificate;
+}
+
+
+enum class KDMCertificatePeriod {
+       KDM_WITHIN_CERTIFICATE,
+       KDM_OVERLAPS_CERTIFICATE,
+       KDM_OUTSIDE_CERTIFICATE
+};
+
+
+/** @param recipient Some KDM recipient certificate.
+ *  @param kdm_from Proposed KDM start time.
+ *  @param kdm_to Proposed KDM end time.
+ *  @return Relationship between certificate and KDM validity periods.
+ */
+
+KDMCertificatePeriod
+check_kdm_and_certificate_validity_periods(dcp::Certificate const& recipient, dcp::LocalTime kdm_from, dcp::LocalTime kdm_to);
+
+
+#endif
+
index 2c821eceb0225fb2f51a23b4bf1a8cb9a78d404f..453a833d79b51b0a2fc1148a973016a20fe921b4 100644 (file)
 */
 
 
-#include "screen.h"
-#include "kdm_with_metadata.h"
-#include "film.h"
 #include "cinema.h"
+#include "film.h"
+#include "kdm_util.h"
+#include "kdm_with_metadata.h"
+#include "screen.h"
 #include <libxml++/libxml++.h>
 #include <boost/algorithm/string.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
@@ -80,7 +81,8 @@ kdm_for_screen (
        boost::posix_time::ptime valid_to,
        dcp::Formulation formulation,
        bool disable_forensic_marking_picture,
-       optional<int> disable_forensic_marking_audio
+       optional<int> disable_forensic_marking_audio,
+       vector<KDMCertificatePeriod>& period_checks
        )
 {
        if (!screen->recipient) {
@@ -91,6 +93,8 @@ kdm_for_screen (
        dcp::LocalTime const begin(valid_from, dcp::UTCOffset(cinema ? cinema->utc_offset_hour() : 0, cinema ? cinema->utc_offset_minute() : 0));
        dcp::LocalTime const end  (valid_to,   dcp::UTCOffset(cinema ? cinema->utc_offset_hour() : 0, cinema ? cinema->utc_offset_minute() : 0));
 
+       period_checks.push_back(check_kdm_and_certificate_validity_periods(screen->recipient.get(), begin, end));
+
        auto const kdm = film->make_kdm (
                        screen->recipient.get(),
                        screen->trusted_device_thumbprints(),
index 84cecb80b4853991cc85b6bfb1c9e95091dceb3c..7cbeb1d153cf979af5d982d96703ba8a05ee26cb 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "kdm_with_metadata.h"
 #include "kdm_recipient.h"
+#include "kdm_util.h"
 #include "trusted_device.h"
 #include <dcp/certificate.h>
 #include <libcxml/cxml.h>
@@ -81,7 +82,8 @@ kdm_for_screen (
        boost::posix_time::ptime valid_to,
        dcp::Formulation formulation,
        bool disable_forensic_marking_picture,
-       boost::optional<int> disable_forensic_marking_audio
+       boost::optional<int> disable_forensic_marking_audio,
+       std::vector<KDMCertificatePeriod>& period_checks
        );
 
 
index ad0bcf0dfca37c8598bca3765889d9c3424a4be2..f8c2d5dc264d190fbcb8a1496db2cd8f2089a6ce 100644 (file)
@@ -140,6 +140,7 @@ sources = """
           kdm_cli.cc
           kdm_recipient.cc
           kdm_with_metadata.cc
+          kdm_util.cc
           log.cc
           log_entry.cc
           make_dcp.cc
index c1239ff29f9ad94b616cf3cd405b0740cbe0b03f..9d6fa2e8f21df8ffd44a05944d657f2bf2d643e9 100644 (file)
@@ -46,6 +46,7 @@
 #include "lib/exceptions.h"
 #include "lib/file_log.h"
 #include "lib/job_manager.h"
+#include "lib/kdm_util.h"
 #include "lib/kdm_with_metadata.h"
 #include "lib/screen.h"
 #include "lib/send_kdm_email_job.h"
@@ -367,6 +368,8 @@ private:
                                throw InvalidSignerError ();
                        }
 
+                       vector<KDMCertificatePeriod> period_checks;
+
                        for (auto i: _screens->screens()) {
 
                                if (!i->recipient) {
@@ -376,6 +379,8 @@ private:
                                dcp::LocalTime begin(_timing->from(), dcp::UTCOffset(i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()));
                                dcp::LocalTime end(_timing->until(), dcp::UTCOffset(i->cinema->utc_offset_hour(), i->cinema->utc_offset_minute()));
 
+                               period_checks.push_back(check_kdm_and_certificate_validity_periods(*i->recipient, begin, end));
+
                                /* Make an empty KDM */
                                dcp::DecryptedKDM kdm (
                                        begin,
@@ -411,6 +416,21 @@ private:
                                return;
                        }
 
+                       if (find(period_checks.begin(), period_checks.end(), KDMCertificatePeriod::KDM_OUTSIDE_CERTIFICATE) != period_checks.end()) {
+                               error_dialog(
+                                       this,
+                                       _("Some KDMs would have validity periods which are completely outside the recipient certificate periods.  Such KDMs are very unlikely to work, so will not be created.")
+                                       );
+                               return;
+                       }
+
+                       if (find(period_checks.begin(), period_checks.end(), KDMCertificatePeriod::KDM_OVERLAPS_CERTIFICATE) != period_checks.end()) {
+                               message_dialog(
+                                       this,
+                                       _("For some of these KDMs the recipient certificate's validity period will not cover the whole of the KDM validity period.  This might cause problems with the KDMs.")
+                                       );
+                       }
+
                        auto result = _output->make (
                                kdms, title, bind (&DOMFrame::confirm_overwrite, this, _1)
                                );
index 79f78b2327a24751f2b86be43e76a2e6285582ae..2687d126162560682bd5bcf1fb948228697fe36a 100644 (file)
@@ -33,6 +33,7 @@
 #include "lib/film.h"
 #include "lib/job_manager.h"
 #include "lib/kdm_with_metadata.h"
+#include "lib/kdm_util.h"
 #include "lib/screen.h"
 #include <libcxml/cxml.h>
 #include <dcp/exceptions.h>
@@ -171,12 +172,30 @@ KDMDialog::make_clicked ()
                        for_audio = _output->forensic_mark_audio_up_to();
                }
 
+               vector<KDMCertificatePeriod> period_checks;
+
                for (auto i: _screens->screens()) {
-                       auto p = kdm_for_screen (film, _cpl->cpl(), i, _timing->from(), _timing->until(), _output->formulation(), !_output->forensic_mark_video(), for_audio);
+                       auto p = kdm_for_screen(film, _cpl->cpl(), i, _timing->from(), _timing->until(), _output->formulation(), !_output->forensic_mark_video(), for_audio, period_checks);
                        if (p) {
                                kdms.push_back (p);
                        }
                }
+
+               if (find(period_checks.begin(), period_checks.end(), KDMCertificatePeriod::KDM_OUTSIDE_CERTIFICATE) != period_checks.end()) {
+                       error_dialog(
+                               this,
+                               _("Some KDMs would have validity periods which are completely outside the recipient certificate periods.  Such KDMs are very unlikely to work, so will not be created.")
+                               );
+                       return;
+               }
+
+               if (find(period_checks.begin(), period_checks.end(), KDMCertificatePeriod::KDM_OVERLAPS_CERTIFICATE) != period_checks.end()) {
+                       message_dialog(
+                               this,
+                               _("For some of these KDMs the recipient certificate's validity period will not cover the whole of the KDM validity period.  This might cause problems with the KDMs.")
+                               );
+               }
+
        } catch (dcp::BadKDMDateError& e) {
                if (e.starts_too_early()) {
                        error_dialog (this, _("The KDM start period is before (or close to) the start of the signing certificate's validity period.  Use a later start time for this KDM."));
index dda30f68880f932f48eebf4a4f0aef895902f0c6..994217a14e7a02878fde4a341ee72cd7cdd48fac 100644 (file)
@@ -98,6 +98,8 @@ BOOST_AUTO_TEST_CASE (single_kdm_naming_test)
        auto const from_string = from.date() + " " + from.time_of_day(true, false);
        auto const until_string = until.date() + " " + until.time_of_day(true, false);
 
+       std::vector<KDMCertificatePeriod> period_checks;
+
        auto cpl = cpls.front().cpl_file;
        auto kdm = kdm_for_screen (
                        film,
@@ -107,7 +109,8 @@ BOOST_AUTO_TEST_CASE (single_kdm_naming_test)
                        boost::posix_time::time_from_string(until_string),
                        dcp::Formulation::MODIFIED_TRANSITIONAL_1,
                        false,
-                       optional<int>()
+                       optional<int>(),
+                       period_checks
                        );
 
        write_files (
@@ -162,6 +165,7 @@ BOOST_AUTO_TEST_CASE (directory_kdm_naming_test, * boost::unit_test::depends_on(
        auto const cpl = cpls.front().cpl_file;
        auto const cpl_id = cpls.front().cpl_id;
 
+       std::vector<KDMCertificatePeriod> period_checks;
        list<KDMWithMetadataPtr> kdms;
        for (auto i: screens) {
                auto kdm = kdm_for_screen (
@@ -172,7 +176,8 @@ BOOST_AUTO_TEST_CASE (directory_kdm_naming_test, * boost::unit_test::depends_on(
                                boost::posix_time::time_from_string(until_string),
                                dcp::Formulation::MODIFIED_TRANSITIONAL_1,
                                false,
-                               optional<int>()
+                               optional<int>(),
+                               period_checks
                                );
 
                kdms.push_back (kdm);
diff --git a/test/kdm_util_test.cc b/test/kdm_util_test.cc
new file mode 100644 (file)
index 0000000..27b9823
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+    Copyright (C) 2023 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/kdm_util.h"
+#include <dcp/certificate.h>
+#include <dcp/util.h>
+#include <boost/test/unit_test.hpp>
+
+
+BOOST_AUTO_TEST_CASE(check_kdm_and_certificate_validity_periods_good)
+{
+       auto const result = check_kdm_and_certificate_validity_periods(
+               dcp::Certificate(dcp::file_to_string("test/data/cert.pem")),
+               dcp::LocalTime("2023-01-03T10:30:00"),
+               dcp::LocalTime("2050-10-20T14:00:00")
+               );
+
+       BOOST_CHECK(result == KDMCertificatePeriod::KDM_WITHIN_CERTIFICATE);
+}
+
+
+BOOST_AUTO_TEST_CASE(check_kdm_and_certificate_validity_periods_overlap_start)
+{
+       auto const result = check_kdm_and_certificate_validity_periods(
+               dcp::Certificate(dcp::file_to_string("test/data/cert.pem")),
+               dcp::LocalTime("2011-01-03T10:30:00"),
+               dcp::LocalTime("2050-10-20T14:00:00")
+               );
+
+       BOOST_CHECK(result == KDMCertificatePeriod::KDM_OVERLAPS_CERTIFICATE);
+}
+
+
+BOOST_AUTO_TEST_CASE(check_kdm_and_certificate_validity_periods_overlap_end)
+{
+       auto const result = check_kdm_and_certificate_validity_periods(
+               dcp::Certificate(dcp::file_to_string("test/data/cert.pem")),
+               dcp::LocalTime("2033-01-03T10:30:00"),
+               dcp::LocalTime("2095-10-20T14:00:00")
+               );
+
+       BOOST_CHECK(result == KDMCertificatePeriod::KDM_OVERLAPS_CERTIFICATE);
+}
+
+
+BOOST_AUTO_TEST_CASE(check_kdm_and_certificate_validity_periods_overlap_start_and_end)
+{
+       auto const result = check_kdm_and_certificate_validity_periods(
+               dcp::Certificate(dcp::file_to_string("test/data/cert.pem")),
+               dcp::LocalTime("2011-01-03T10:30:00"),
+               dcp::LocalTime("2095-10-20T14:00:00")
+               );
+
+       BOOST_CHECK(result == KDMCertificatePeriod::KDM_OVERLAPS_CERTIFICATE);
+}
+
+
+BOOST_AUTO_TEST_CASE(check_kdm_and_certificate_validity_periods_outside)
+{
+       auto const result = check_kdm_and_certificate_validity_periods(
+               dcp::Certificate(dcp::file_to_string("test/data/cert.pem")),
+               dcp::LocalTime("2011-01-03T10:30:00"),
+               dcp::LocalTime("2012-10-20T14:00:00")
+               );
+
+       BOOST_CHECK(result == KDMCertificatePeriod::KDM_OUTSIDE_CERTIFICATE);
+}
index 5807d94ea9937f5dcc15d30d071b28d55a12b910..5e06ed5e21131d5da406bcdbf1f5738995655114 100644 (file)
@@ -108,6 +108,7 @@ def build(bld):
                  job_manager_test.cc
                  kdm_cli_test.cc
                  kdm_naming_test.cc
+                 kdm_util_test.cc
                  low_bitrate_test.cc
                  markers_test.cc
                  no_use_video_test.cc