2 Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
35 #include "certificate_chain.h"
37 #include "decrypted_kdm.h"
38 #include "encrypted_kdm.h"
39 #include "mono_picture_asset.h"
40 #include "picture_asset_writer.h"
42 #include "reel_mono_picture_asset.h"
43 #include "reel_sound_asset.h"
44 #include "reel_smpte_subtitle_asset.h"
45 #include "smpte_subtitle_asset.h"
50 #include <libcxml/cxml.h>
51 LIBDCP_DISABLE_WARNINGS
52 #include <libxml++/libxml++.h>
53 LIBDCP_ENABLE_WARNINGS
54 #include <boost/test/unit_test.hpp>
57 using std::dynamic_pointer_cast;
58 using std::make_shared;
59 using std::shared_ptr;
62 using boost::optional;
65 /** Check reading and decryption of a KDM */
66 BOOST_AUTO_TEST_CASE (kdm_test)
68 dcp::DecryptedKDM kdm (
70 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
72 dcp::file_to_string ("test/data/private.key")
75 auto keys = kdm.keys ();
77 BOOST_CHECK_EQUAL (keys.size(), 2U);
79 BOOST_CHECK_EQUAL (keys.front().cpl_id(), "eece17de-77e8-4a55-9347-b6bab5724b9f");
80 BOOST_CHECK_EQUAL (keys.front().id(), "4ac4f922-8239-4831-b23b-31426d0542c4");
81 BOOST_CHECK_EQUAL (keys.front().key().hex(), "8a2729c3e5b65c45d78305462104c3fb");
83 BOOST_CHECK_EQUAL (keys.back().cpl_id(), "eece17de-77e8-4a55-9347-b6bab5724b9f");
84 BOOST_CHECK_EQUAL (keys.back().id(), "73baf5de-e195-4542-ab28-8a465f7d4079");
85 BOOST_CHECK_EQUAL (keys.back().key().hex(), "5327fb7ec2e807bd57059615bf8a169d");
89 /** Check that we can read in a KDM and then write it back out again the same */
90 BOOST_AUTO_TEST_CASE (kdm_passthrough_test)
92 dcp::EncryptedKDM kdm (
93 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
96 auto parser = make_shared<xmlpp::DomParser>();
97 parser->parse_memory (kdm.as_xml ());
98 parser->get_document()->write_to_file_formatted ("build/kdm.xml", "UTF-8");
100 dcp::file_to_string("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml"),
101 dcp::file_to_string("build/kdm.xml"),
108 /** Test some of the utility methods of DecryptedKDM */
109 BOOST_AUTO_TEST_CASE (decrypted_kdm_test)
111 auto data = new uint8_t[16];
113 dcp::DecryptedKDM::put_uuid (&p, "8971c838-d0c3-405d-bc57-43afa9d91242");
115 BOOST_CHECK_EQUAL (data[0], 0x89);
116 BOOST_CHECK_EQUAL (data[1], 0x71);
117 BOOST_CHECK_EQUAL (data[2], 0xc8);
118 BOOST_CHECK_EQUAL (data[3], 0x38);
119 BOOST_CHECK_EQUAL (data[4], 0xd0);
120 BOOST_CHECK_EQUAL (data[5], 0xc3);
121 BOOST_CHECK_EQUAL (data[6], 0x40);
122 BOOST_CHECK_EQUAL (data[7], 0x5d);
123 BOOST_CHECK_EQUAL (data[8], 0xbc);
124 BOOST_CHECK_EQUAL (data[9], 0x57);
125 BOOST_CHECK_EQUAL (data[10], 0x43);
126 BOOST_CHECK_EQUAL (data[11], 0xaf);
127 BOOST_CHECK_EQUAL (data[12], 0xa9);
128 BOOST_CHECK_EQUAL (data[13], 0xd9);
129 BOOST_CHECK_EQUAL (data[14], 0x12);
130 BOOST_CHECK_EQUAL (data[15], 0x42);
133 BOOST_CHECK_EQUAL (dcp::DecryptedKDM::get_uuid (&p), "8971c838-d0c3-405d-bc57-43afa9d91242");
139 /** Check that <KeyType> tags have the scope attribute.
140 * Wolfgang Woehl believes this is compulsory and I am more-or-less inclined to agree.
142 BOOST_AUTO_TEST_CASE (kdm_key_type_scope)
144 dcp::EncryptedKDM kdm (
145 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
149 doc.read_string (kdm.as_xml ());
151 auto typed_key_ids = doc.node_child("AuthenticatedPublic")->
152 node_child("RequiredExtensions")->
153 node_child("KDMRequiredExtensions")->
154 node_child("KeyIdList")->
155 node_children("TypedKeyId");
157 for (auto i: typed_key_ids) {
158 for (auto j: i->node_children("KeyType")) {
159 BOOST_CHECK (j->string_attribute("scope") == "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
165 static cxml::ConstNodePtr
166 kdm_forensic_test (cxml::Document& doc, bool picture, optional<int> audio)
168 dcp::DecryptedKDM decrypted (
170 dcp::file_to_string ("test/data/kdm_TONEPLATES-SMPTE-ENC_.smpte-430-2.ROOT.NOT_FOR_PRODUCTION_20130706_20230702_CAR_OV_t1_8971c838.xml")
172 dcp::file_to_string ("test/data/private.key")
175 auto signer = make_shared<dcp::CertificateChain>(dcp::file_to_string("test/data/certificate_chain"));
176 signer->set_key(dcp::file_to_string("test/data/private.key"));
178 dcp::EncryptedKDM kdm = decrypted.encrypt (
179 signer, signer->leaf(), vector<string>(), dcp::Formulation::MODIFIED_TRANSITIONAL_1, picture, audio
182 /* Check that we can pass this through correctly */
183 BOOST_CHECK_EQUAL (kdm.as_xml(), dcp::EncryptedKDM(kdm.as_xml()).as_xml());
185 doc.read_string (kdm.as_xml());
187 return doc.node_child("AuthenticatedPublic")->
188 node_child("RequiredExtensions")->
189 node_child("KDMRequiredExtensions")->
190 optional_node_child("ForensicMarkFlagList");
194 /** Check ForensicMarkFlagList handling: disable picture and all audio */
195 BOOST_AUTO_TEST_CASE (kdm_forensic_test1)
198 auto forensic = kdm_forensic_test(doc, true, 0);
199 BOOST_REQUIRE (forensic);
200 auto flags = forensic->node_children("ForensicMarkFlag");
201 BOOST_REQUIRE_EQUAL (flags.size(), 2U);
202 BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
203 BOOST_CHECK_EQUAL (flags.back()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
207 /** Check ForensicMarkFlagList handling: disable picture but not audio */
208 BOOST_AUTO_TEST_CASE (kdm_forensic_test2)
211 auto forensic = kdm_forensic_test(doc, true, optional<int>());
212 BOOST_REQUIRE (forensic);
213 auto flags = forensic->node_children("ForensicMarkFlag");
214 BOOST_REQUIRE_EQUAL (flags.size(), 1U);
215 BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
219 /** Check ForensicMarkFlagList handling: disable audio but not picture */
220 BOOST_AUTO_TEST_CASE (kdm_forensic_test3)
223 auto forensic = kdm_forensic_test(doc, false, 0);
224 BOOST_REQUIRE (forensic);
225 auto flags = forensic->node_children("ForensicMarkFlag");
226 BOOST_REQUIRE_EQUAL (flags.size(), 1U);
227 BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
231 /** Check ForensicMarkFlagList handling: disable picture and audio above channel 3 */
232 BOOST_AUTO_TEST_CASE (kdm_forensic_test4)
235 auto forensic = kdm_forensic_test(doc, true, 3);
236 BOOST_REQUIRE (forensic);
237 auto flags = forensic->node_children("ForensicMarkFlag");
238 BOOST_REQUIRE_EQUAL (flags.size(), 2U);
239 BOOST_CHECK_EQUAL (flags.front()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
240 BOOST_CHECK_EQUAL (flags.back()->content(), "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable-above-channel-3");
244 /** Check ForensicMarkFlagList handling: disable neither */
245 BOOST_AUTO_TEST_CASE (kdm_forensic_test5)
248 auto forensic = kdm_forensic_test(doc, false, optional<int>());
249 BOOST_CHECK (!forensic);
253 /** Check that KDM validity periods are checked for being within the certificate validity */
254 BOOST_AUTO_TEST_CASE (validity_period_test1)
256 auto signer = make_shared<dcp::CertificateChain>(dcp::file_to_string("test/data/certificate_chain"));
257 signer->set_key(dcp::file_to_string("test/data/private.key"));
259 auto asset = make_shared<dcp::MonoPictureAsset>(dcp::Fraction(24, 1), dcp::Standard::SMPTE);
260 asset->set_key (dcp::Key());
261 auto writer = asset->start_write("build/test/validity_period_test1.mxf", dcp::PictureAsset::Behaviour::MAKE_NEW);
262 dcp::ArrayData frame ("test/data/flat_red.j2c");
263 writer->write (frame.data(), frame.size());
265 auto reel = make_shared<dcp::Reel>();
266 reel->add(make_shared<dcp::ReelMonoPictureAsset>(asset, 0));
267 auto cpl = make_shared<dcp::CPL>("test", dcp::ContentKind::FEATURE, dcp::Standard::SMPTE);
270 /* This certificate_chain is valid from 22/12/2022 to 19/12/2032 */
273 BOOST_CHECK_NO_THROW(
275 cpl, dcp::Key(dcp::file_to_string("test/data/private.key")), dcp::LocalTime("2025-01-01T00:00:00"), dcp::LocalTime("2027-07-31T00:00:00"), "", "", ""
276 ).encrypt(signer, signer->leaf(), vector<string>(), dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, optional<int>())
279 /* Starts too early */
282 cpl, dcp::Key(dcp::file_to_string("test/data/private.key")), dcp::LocalTime("1981-01-01T00:00:00"), dcp::LocalTime("2017-07-31T00:00:00"), "", "", ""
283 ).encrypt(signer, signer->leaf(), vector<string>(), dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
287 /* Finishes too late */
290 cpl, dcp::Key(dcp::file_to_string("test/data/private.key")), dcp::LocalTime("2015-01-01T00:00:00"), dcp::LocalTime("2035-07-31T00:00:00"), "", "", ""
291 ).encrypt(signer, signer->leaf(), vector<string>(), dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
295 /* Starts too early and finishes too late */
298 cpl, dcp::Key(dcp::file_to_string("test/data/private.key")), dcp::LocalTime("1981-01-01T00:00:00"), dcp::LocalTime("2035-07-31T00:00:00"), "", "", ""
299 ).encrypt(signer, signer->leaf(), vector<string>(), dcp::Formulation::MODIFIED_TRANSITIONAL_1, true, optional<int>()),
305 /** Test the case where we have:
307 * - a single KDM which has keys for both OV and VF assets
308 * and we load VF, then KDM, then the OV. The OV's assets should have their
309 * KDMs properly assigned.
311 BOOST_AUTO_TEST_CASE (vf_kdm_test)
315 boost::filesystem::path const ov_path = "build/test/vf_kdm_test_ov";
316 boost::filesystem::path const vf_path = "build/test/vf_kdm_test_vf";
319 auto ov = make_simple(ov_path, 1, 48, dcp::Standard::SMPTE, key);
322 auto ov_reel = ov->cpls()[0]->reels()[0];
323 auto ov_reel_picture = make_shared<dcp::ReelMonoPictureAsset>(dynamic_pointer_cast<dcp::ReelMonoPictureAsset>(ov_reel->main_picture())->mono_asset(), 0);
324 auto ov_reel_sound = make_shared<dcp::ReelSoundAsset>(ov_reel->main_sound()->asset(), 0);
328 auto subs = make_shared<dcp::SMPTESubtitleAsset>();
329 subs->add(simple_subtitle());
332 boost::filesystem::remove_all (vf_path);
333 boost::filesystem::create_directory (vf_path);
334 dcp::ArrayData data(4096);
335 subs->add_font ("afont", data);
336 subs->write (vf_path / "subs.xml");
338 auto reel_subs = make_shared<dcp::ReelSMPTESubtitleAsset>(subs, dcp::Fraction(24, 1), 192, 0);
340 auto reel = make_shared<dcp::Reel>(ov_reel_picture, ov_reel_sound, reel_subs);
342 auto cpl = make_shared<dcp::CPL>("A Test DCP", dcp::ContentKind::TRAILER, dcp::Standard::SMPTE);
345 auto vf = make_shared<dcp::DCP>("build/test/vf_kdm_test_vf");
349 /* Make KDM for VF */
351 auto kdm = dcp::DecryptedKDM(
354 dcp::LocalTime ("2016-01-01T00:00:00+00:00"),
355 dcp::LocalTime ("2017-01-08T00:00:00+00:00"),
358 "2012-07-17T04:45:18+00:00"
361 /* Decrypt VF with the KDM */
363 dcp::DCP reload_vf(vf_path);
369 dcp::DCP reload_ov(ov_path);
371 reload_vf.resolve_refs(reload_ov.assets());
373 /* Check that we can decrypt the VF */
375 BOOST_REQUIRE_EQUAL(reload_vf.cpls().size(), 1U);
376 BOOST_REQUIRE_EQUAL(reload_vf.cpls()[0]->reels().size(), 1U);
377 BOOST_REQUIRE(reload_vf.cpls()[0]->reels()[0]->main_picture());
378 BOOST_REQUIRE(reload_vf.cpls()[0]->reels()[0]->main_picture()->asset());
379 auto mono_asset = dynamic_pointer_cast<dcp::MonoPictureAsset>(reload_vf.cpls()[0]->reels()[0]->main_picture()->asset());
380 BOOST_REQUIRE(mono_asset);
381 auto reader = mono_asset->start_read();
382 reader->set_check_hmac(false);
383 reader->get_frame(0)->xyz_image();